目錄
引言
1.請設計一個類,不能被拷貝
2. 請設計一個類,只能在堆上創建對象
為什么設置實例的方法為靜態成員呢
3. 請設計一個類,只能在棧上創建對象
4. 請設計一個類,不能被繼承
5. 請設計一個類,只能創建一個對象(單例模式)
餓漢模式
懶漢模式
單例對象一般不考慮析構
為什么私有析構呢?
引言
在當今的軟件開發實踐中,特殊類設計模式扮演著至關重要的角色,它們不僅提高了代碼的可維護性和可擴展性,還確保了系統的穩定性和性能。其中,單例模式作為最常用的設計模式之一,以其獨特的實例管理方式,成為了許多場景下的首選解決方案。本文旨在探討C++中單例模式及其他特殊類設計的精髓,通過分析其背后的設計哲學和實現技巧,幫助讀者掌握這些高級編程技巧,以提升其在軟件開發過程中的設計能力和代碼質量。
在我們設計一個特殊類的時候,一般要把構造、拷貝和賦值私有保護,防止被其他類拷貝或者產生不想要(意料之外)的錯誤。
1.請設計一個類,不能被拷貝
拷貝只會放生在兩個場景中:拷貝構造函數以及賦值運算符重載,因此想要讓一個類禁止拷貝,
class CopyBan
{// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//...
};
C++11
class CopyBan
{// ...CopyBan(const CopyBan&)=delete;CopyBan& operator=(const CopyBan&)=delete;//...
};
同時我們還可以設置一個基類,讓子類繼承基類,讓子類正常實現功能,設置基類不可被拷貝即可。
2. 請設計一個類,只能在堆上創建對象
class HeapOnly ? ?
{ ? ?
public: ? ? static HeapOnly* CreateObject() ?{ ? ? ?return new HeapOnly; ? ?}
private: ? ?HeapOnly() {}// C++98// 1.只聲明,不實現。因為實現可能會很麻煩,而你本身不需要// 2.聲明成私有HeapOnly(const HeapOnly&);// or// C++11 ? ?HeapOnly(const HeapOnly&) = delete;
}
由于沒有this指針,無法調用構造,而是只是去new這個鍍錫,由系統去完成資源的申請
靜態不能調用非靜態就是因為沒有this指針,缺少調用的參數
但是new和delete這種操作不需要this指針
那每次調的時候,返回的都是一個新的靜態對象嗎?--- NO!這個方法是靜態的,但是返回的變量不是靜態的
為什么設置實例的方法為靜態成員呢
收線確定的是,一定是內部new創建的對象。
因為如果設置為非靜態成員,那就只能通過對象去調用,但是我們不能去在棧區創建對象。
并且構造私有,外部一定不可以創建對象,只能用類調用,所以必須靜態!
為什么禁用拷貝構造:比如A a1(a2);這樣a1依然是在棧區!
3. 請設計一個類,只能在棧上創建對象
只能通過類型創建,禁止new對象
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly obj;return obj;}void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
private:StackOnly() ?:_a(0){}
private:int _a;
};
1.設置為靜態:
并且構造私有,外部一定不可以創建對象,只能用類調用,所以必須靜態!
2.為什么不可以禁用拷貝構造:比如A a1(a2);這樣a1依然是在棧區!符合要求,并且匿名對象的聲明周期只在這一行!為了防止匿名對象周期太短,無法進行較好的連續性開發,所以需要賦值給另一個棧區的對象。
3.可以返回臨時對象,只需要賦值給其他對象即可(棧區);也可以返回非匿名對象,也是采用賦值的方式進行連續性開發。
4.為了防止創建堆區對象,需要禁用new和delete(為了防止創建棧區對象時,需要禁用拷貝、賦值)
????????
4. 請設計一個類,不能被繼承
C++98方式
// C++98中構造函數私有化,派生類中調不到基類的構造函數。則無法繼承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
class A ?final
{// ....
};
5. 請設計一個類,只能創建一個對象(單例模式)
餓漢模式
餓漢:設置為全局,提前創建。
既然是單例,一定先把構造私有。
在類的外部雖然不能創建全局的對象,但是在類的內部可以創建。----思路:類的內部聲明靜態成員,類的外部定義,這樣就是一個全局的數據。
// 懶漢模式:第一次用的時候再創建(現吃現做)
class A {
public:static A* GetInstance() {return _inst;}
private:A() {}map<string, string> _dict;int _n = 0;static A* _inst;
};
我們在類的內部創建對象的時候,當然不能
? ?class? ? ? ? A
{
? ? ? ? A? ?_a;
??}



訪問成員需要借助對象,這時候就需要調用GetInstance函數
優點:創建簡單?
缺點:1.進程啟動慢
?????????? 2.一旦存在兩個全局單例,不能控制單例啟動的先后順序。
懶漢模式
現吃現做,不著急,main函數內創建。
? 只需要把靜態的對象換成指針即可,獲取實例的時候,如果指針是nullptr,那么創建,如果不是,那么直接返回
線程不安全,兩個線程同時進來,可能會new兩個對象---加鎖
單例對象一般不考慮析構
惡漢不存在釋放的問題--全局,
懶漢對象也一般不需要釋放---單例對象一般是生命周期伴隨整個程序,對整個程序都起到至關重要的作用,進程結束時,會自動釋放。
就算要釋放,如果我們期望既可以手動釋放(析構不能手動釋放),也可以在main函數結束時自動釋放
class B
{
public:static B* GetInstance(){if (_inst == nullptr){_inst = new B;}return _inst;}static void DelInstance(){if (_inst){delete _inst;_inst = nullptr;}}private:B(){}~B(){// 持久化:要求把數據寫到文件cout << "數據寫到文件" << endl;}B(const B& aa) = delete;B& operator=(const B& aa) = delete;map<string, string> _dict;int _n = 0;static B* _inst;class gc{public:~gc(){DelInstance();}};static gc _gc;
};B* B::_inst = nullptr;
B::gc B::_gc;
可以私有析構函數
提供一個調用析構函數的接口DelInstance,我們可以手動調用這個接口去進行析構。
我們新建一個內部類,這個內部類是外部類的友元,可以訪問私有。等main函數結束的時候_gc調用析構,析構會調用DelInstance。
這樣可以做到:1.顯示釋放(析構不允許顯示調用,但是delete指針的時候,可以調用析構)
? ? ? ? ? ? ? ? ? ? ? ? 2.沒有顯示釋放,等程序結束也會釋放
為什么私有析構呢?
B* instance = B::GetInstance();
delete instance;
B* instance2 = B::GetInstance();
如果是public的話,每個人可以自己delete這個,那么就違背單例模式的原則了!
而這塊放的這個DelInstance函數是意思萬一情況下,不使用單例了,自己想釋放的話,可以通過這個函數釋放,但是一般也不會這樣做的,一般gc就替咱們回收了
總之就是:不能讓其他人去隨便delete單例,所以私有;同時希望gc能夠管理單例,等程序結束,gc清理的時候,gc會協助清理單例