一篇文章帶你了解C++特殊類的設(shè)計
設(shè)計一個類,只能在堆上創(chuàng)建對象
想要的效果實際是沒法直接在棧上創(chuàng)建對象。
首先cpp只要創(chuàng)建對象就要調(diào)用構(gòu)造函數(shù),因此先要把構(gòu)造函數(shù)ban掉,把構(gòu)造函數(shù)設(shè)計成private。但是單這樣自己也創(chuàng)建不了了。
因此提供一個創(chuàng)建的接口,只能調(diào)用該接口,該接口內(nèi)部寫new。而且要調(diào)用該接口需要先有對象指針調(diào)用,而要有對象先得調(diào)用構(gòu)造函數(shù)實例化,因此必須設(shè)計成靜態(tài)函數(shù)。
但是注意這樣還有拷貝函數(shù)可以調(diào)用HeapOnly copy(*p)。此時生成的也是棧上的對象。因此要拷貝構(gòu)造私有,并且只聲明不實現(xiàn)(實現(xiàn)也是可以的,但是沒人用)。這種方式在c++98中叫防拷貝,比如互斥鎖。
#include<iostream>
using namespace std;
class HeapOnly
{
private:
HeapOnly()
{ }
//C++98——防拷貝
HeapOnly(const HeapOnly&);
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
};
int main()
{
HeapOnly* p = HeapOnly::CreateObj();
return 0;
}
對于防拷貝,C++11中有新的方式。函數(shù)=delete。
#include<iostream>
using namespace std;
class HeapOnly
{
private:
HeapOnly()
{ }
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
//C++11——防拷貝
HeapOnly(const HeapOnly&) =delete;
};
int main()
{
HeapOnly* p = HeapOnly::CreateObj();
return 0;
}
總結(jié):
1.將類的構(gòu)造函數(shù)私有,拷貝構(gòu)造聲明成私有。防止別人調(diào)用拷貝在棧上生成對象。
2.提供一個靜態(tài)的成員函數(shù),在該靜態(tài)成員函數(shù)中完成堆對象的創(chuàng)建
設(shè)計一個類,只能在棧上創(chuàng)建對象
- 方法一:同上將構(gòu)造函數(shù)私有化,然后設(shè)計靜態(tài)方法創(chuàng)建對象返回即可。
由于返回臨時對象,因此不能禁掉拷貝構(gòu)造。
class StackOnly
{
public:
static StackOnly CreateObject()
{
return StackOnly();
}
private:
StackOnly() {}
};
- 方法二:調(diào)用類自己的專屬的operator new和operator delete,設(shè)置為私有。
因為new在底層調(diào)用void* operator new(size_t size)函數(shù),只需將該函數(shù)屏蔽掉即可。注意:也要防止定位new。new先調(diào)用operator new申請空間,然后調(diào)用構(gòu)造函數(shù)。delete先調(diào)用析構(gòu)函數(shù)釋放對象所申請的空間,再調(diào)用operator delete釋放申請的對象空間。
class StackOnly
{
public:
StackOnly() {}
private: //C++98
void* operator new(size_t size);
void operator delete(void* p);
};
int main()
{
static StackOnly st;//缺陷,沒有禁掉靜態(tài)區(qū)的。
}
class StackOnly
{
public:
StackOnly() {}
//C++11
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
};
int main()
{
static StackOnly st;//缺陷,沒有禁掉靜態(tài)區(qū)的。
}
設(shè)計一個類,不能被拷貝
拷貝只會放生在兩個場景中:拷貝構(gòu)造函數(shù)以及賦值運算符重載,因此想要讓一個類禁止拷貝,只需讓該類不能調(diào)用拷貝構(gòu)造函數(shù)以及賦值運算符重載即可。
- C++98將拷貝構(gòu)造函數(shù)與賦值運算符重載只聲明不定義,并且將其訪問權(quán)限設(shè)置為私有即可。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
1.設(shè)置成私有:如果只聲明沒有設(shè)置成private,用戶自己如果在類外定義了,就可以不能禁止拷貝了
2.只聲明不定義:不定義是因為該函數(shù)根本不會調(diào)用,定義了其實也沒有什么意義,不寫反而還簡單,而且如果定義了就不會防止成員函數(shù)內(nèi)部拷貝了。
- C++11擴展delete的用法,delete除了釋放new申請的資源外,如果在默認成員函數(shù)后跟上=delete,表示讓編譯器刪除掉該默認成員函數(shù)。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
設(shè)計一個類,不能繼承
C++98
// C++98中構(gòu)造函數(shù)私有化,派生類中調(diào)不到基類的構(gòu)造函數(shù)。則無法繼承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
class B : public NonInherit
{};
int main()
{
//C++98中這個不能被繼承的方式不夠徹底,實際是可以繼承,限制的是子類繼承后不能實例化對象
B b;
return 0;
}
C++11為了更直觀,加入了final關(guān)鍵字
class A final
{ };
class C: A
{};
設(shè)計一個類,只能創(chuàng)建一個對象(單例模式)
之前接觸過了適配器模式和迭代器模式。
可以再看看工廠模式,觀察者模式等等常用一兩個的。
單例模式的概念
設(shè)計模式:設(shè)計模式(Design Pattern)是一套被反復使用、多數(shù)人知曉的、經(jīng)過分類的、代碼設(shè)計經(jīng)驗的總結(jié)。
為什么會產(chǎn)生設(shè)計模式這樣的東西呢?就像人類歷史發(fā)展會產(chǎn)生兵法。最開始部落之間打仗時都是人拼人的對砍。后來春秋戰(zhàn)國時期,七國之間經(jīng)常打仗,就發(fā)現(xiàn)打仗也是有套路的,后來孫子就總結(jié)出了《孫子兵法》。孫子兵法也是類似。
使用設(shè)計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。
設(shè)計模式使代碼編寫真正工程化;設(shè)計模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣。
- 單例模式
一個類只能創(chuàng)建一個對象,即單例模式,該模式可以保證系統(tǒng)中該類只有一個實例,并提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。比如在某個服務(wù)器程序中,該服務(wù)器的配置信息存放在一個文件中,這些配置數(shù)據(jù)由一個單例對象統(tǒng)一讀取,然后服務(wù)進程中的其他對象再通過這個單例對象獲取這些配置信息,這種方式簡化了在復雜環(huán)境下的配置管理。
1.如何保證全局(一個進程中)只有一個唯一的實例對象
參考只能在堆上創(chuàng)建對象和在棧上創(chuàng)建對象,禁止構(gòu)造和拷貝構(gòu)造及賦值。
提供一個GetInstance獲取單例對象。
2.如何提供只有一個實例呢?
餓漢模式和懶漢模式。
單例模式的實現(xiàn)
餓漢模式
餓漢模式:程序開始main執(zhí)行之前就創(chuàng)建單例對象,提供一個靜態(tài)指向單例對象的成員指針,初始時new一個對象給它。
class Singleton
{
public:
static Singleton* GetInstance()
{
return _inst;
}
void Print()
{
cout<<"Print() "<<_val<<endl;
}
private:
Singleton()
:_val(0)
{}
Singleton(const Singleton& ) =delete;
Singleton(const Singleton& ) =delete;
static Singleton* _inst;
int _val;
};
Singleton* Singleton::_inst = new Singleton;
int main()
{
cout<<Singleton::GetInstance()<<endl;
cout<<Singleton::GetInstance()<<endl;
cout<<Singleton::GetInstance()<<endl;
Singleton::GetInstance()->Print();
}
懶漢模式
懶漢模式:
懶漢模式出現(xiàn)的原因,單例類的構(gòu)造函數(shù)中要做很多配置初始化工作,那么餓漢就不合適了,會導致程序啟動很慢。
linux是Posix的pthread庫,windows下有自己的線程庫。因此要使用條件編譯保證兼容性。因此c++11為了規(guī)范提供了語言級別的封裝(本質(zhì)也是條件編譯,庫里實現(xiàn)了)。
關(guān)于保護第一次需要加鎖,后面都不需要加鎖的場景的可以使用雙檢查加鎖。
#include<mutex>
#ifdef _WIN32
//windos 提供多線程api
#else
//linux pthread
#endif //
class Singleton
{
public:
static Singleton* GetInstance()
{
//保護第一次需要加鎖,后面都不需要加鎖的場景,可以使用雙檢查加鎖
//特點:第一次加鎖,后面不加鎖,保護線程安全,同時提高了效率
if( _inst == nullptr)
{
_mtx.lock();
if( _inst == nullptr )
{
_inst = new Singleton;
}
_ntx.unlock();
}
return _inst;
}
void Print()
{
cout<<"Print() "<<_val<<endl;
}
private:
Singleton()
:_val(0)
{}
Singleton(const Singleton& ) =delete;
Singleton(const Singleton& ) =delete;
static Singleton* _inst;
static std::mutex _mtx;
int _val;
};
Singleton* Singleton::_inst = nullptr;
std::mutex Singleton::_mtx;//()默認無參構(gòu)造函數(shù)
int main()
{
Singleton::GetInstance()->Print();
}
餓漢模式和懶漢模式的對比
- 餓漢模式
- 優(yōu)點:簡單
- 缺點:
- 如果單例對象構(gòu)造函數(shù)工作比較多,會導致程序啟動慢,遲遲進不了入口main函數(shù)。
- 如果有多個單例對象,他們之間有初始化的依賴關(guān)系,餓漢模式也會有問題。比如有A和B兩個單例類,要求A單例先初始化,B必須在A之后初始化,那么餓漢無法保證。這種場景下用懶漢模式,懶漢可以先調(diào)用A::GetInstance(),再調(diào)用B::GetInstance()。
- 懶漢模式
- 優(yōu)點:解決了餓漢的缺點,因為他是第一次調(diào)用GetInstance時創(chuàng)建初始化單例對象
- 缺點:相對餓漢復雜一點。
懶漢模式的優(yōu)化
實現(xiàn)了”更懶“。
缺點:單例對象在靜態(tài)區(qū),如果單例對象太大,不合適。再挑挑刺,這個靜態(tài)對象無法主動控制釋放。
#include<mutex>
#ifdef _WIN32
//windos 提供多線程api
#else
//linux pthread
#endif //
//其他版本懶漢
class Singleton
{
public:
static Singleton* GetInstance()
{
static Singleton inst;
return &inst;
}
void Print()
{
cout<<"Print() "<<_val<<endl;
}
private:
Singleton()
:_val(0)
{}
Singleton(const Singleton& ) =delete;
Singleton(const Singleton& ) =delete;
static std::mutex _mtx;
int _val;
};
std::mutex Singleton::_mtx;//()默認無參構(gòu)造函數(shù)
int main()
{
Singleton::GetInstance()->Print();
}
#include<mutex>
#ifdef _WIN32
//windos 提供多線程api
#else
//linux pthread
#endif //
//其他版本懶漢
class Singleton
{
public:
static Singleton* GetInstance()
{
static Singleton inst;
return &inst;
}
void Print()
{
cout<<"Print() "<<_val<<endl;
}
private:
Singleton()
:_val(0)
{}
Singleton(const Singleton& ) =delete;
Singleton(const Singleton& ) =delete;
static std::mutex _mtx;
int _val;
};
std::mutex Singleton::_mtx;//()默認無參構(gòu)造函數(shù)
int main()
{
Singleton::GetInstance()->Print();
}
單例對象的釋放
單例對象一般不需要釋放。全局一直用的不delete也沒問題,進程如果正常銷毀,進程會釋放對應(yīng)資源。
單例對象的直接釋放
#include<mutex>
#ifdef _WIN32
//windos 提供多線程api
#else
//linux pthread
#endif //
class Singleton
{
public:
static Singleton* GetInstance()
{
//保護第一次需要加鎖,后面都不需要加鎖的場景,可以使用雙檢查加鎖
//特點:第一次加鎖,后面不加鎖,保護線程安全,同時提高了效率
if( _inst == nullptr)
{
_mtx.lock();
if( _inst == nullptr )
{
_inst = new Singleton;
}
_ntx.unlock();
}
return _inst;
}
static void DelInstance()/*調(diào)的很少,可以雙檢查也可以不雙檢查*/
{
_mtx.lock();
if(!_inst)
{
delete _inst;
_inst=nullptr;
}
_mtx.unlock();
}
void Print()
{
cout<<"Print() "<<_val<<endl;
}
private:
Singleton()
:_val(0)
{
//假設(shè)單例類構(gòu)造函數(shù)中,需要做很多配置初始化
}
~Singletion()
{
//程序結(jié)束時,需要處理一下,持久化保存一些數(shù)據(jù)
}
Singleton(const Singleton& ) =delete;
Singleton(const Singleton& ) =delete;
static Singleton* _inst;
static std::mutex _mtx;
int _val;
};
Singleton* Singleton::_inst = nullptr;
std::mutex Singleton::_mtx;//()默認無參構(gòu)造函數(shù)
int main()
{
Singleton::GetInstance()->Print();
}
內(nèi)部垃圾回收類
上述場景其實還是可以擴展的。
假設(shè)析構(gòu)函數(shù)有一些數(shù)據(jù)需要保存一下,持久化一下,不調(diào)用析構(gòu)函數(shù)會存在問題,因此需要調(diào)用析構(gòu)函數(shù)的時候處理。這就得保證main函數(shù)結(jié)束的時候保證調(diào)用析構(gòu)(private)。
但是顯式調(diào)用DelInstance可能會存在遺忘。
#include<mutex>
#ifdef _WIN32
//windos 提供多線程api
#else
//linux pthread
#endif //
class Singleton
{
public:
static Singleton* GetInstance()
{
//保護第一次需要加鎖,后面都不需要加鎖的場景,可以使用雙檢查加鎖
//特點:第一次加鎖,后面不加鎖,保護線程安全,同時提高了效率
if( _inst == nullptr)
{
_mtx.lock();
if( _inst == nullptr )
{
_inst = new Singleton;
}
_ntx.unlock();
}
return _inst;
}
void Print()
{
cout<<"Print() "<<_val<<endl;
}
private:
Singleton()
:_val(0)
{
//假設(shè)單例類構(gòu)造函數(shù)中,需要做很多配置初始化
}
~Singletion()
{
//程序結(jié)束時,需要處理一下,持久化保存一些數(shù)據(jù)
}
Singleton(const Singleton& ) =delete;
Singleton(const Singleton& ) =delete;
//實現(xiàn)一個內(nèi)嵌垃圾回收類
class CGarbo{
public:
~CGarbo()
{
//DelInstance();
if(_inst)
{
delete _inst;
_inst = nullptr;
}
}
}
static Singleton* _inst;
static std::mutex _mtx;
static GCarbo _gc;//定義靜態(tài)gc對象,幫助我們進行回收
int _val;
};
Singleton* Singleton::_inst = nullptr;
std::mutex Singleton::_mtx;//()默認無參構(gòu)造函數(shù)
Singleton::CGarbo Singleton::_gc;
int main()
{
Singleton::GetInstance()->Print();
}
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
簡述C語言中system()函數(shù)與vfork()函數(shù)的使用方法
這篇文章主要介紹了簡述C語言中system()函數(shù)與vfork()函數(shù)的使用方法,是C語言入門學習中的基礎(chǔ)知識,需要的朋友可以參考下2015-08-08
C++數(shù)據(jù)封裝以及定義結(jié)構(gòu)的詳細講解
這篇文章主要詳細講解了C++數(shù)據(jù)封裝以及定義結(jié)構(gòu),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-05-05

