文章目錄
- 前言
- 一、 設計一個不能被拷貝的類
- 1. C++98 實現方式
- 2. C++11 實現方式
- 二、設計一個只能在堆上創建對象的類
- 1. 方法一:析構函數私有,提供destory接口釋放資源
- 2. 方法二:構造函數私有
- 三、 設計一個只能在棧上創建對象的類
- 1. 實現方式
- 四、設計一個不能被繼承的類
- 1. C++98 實現方式
- 2. C++11 實現方式
- 五、設計一個只能創建一個對象(單例模式)
- 1. 單例模式介紹
- 2. 餓漢模式(Eager Singleton)
- (1)餓漢模式的實現步驟
- (2)代碼解析
- (3)餓漢模式的優缺點
- 3. 懶漢模式(Lazy Singleton)
- (1)為什么使用懶漢模式?
- (2)代碼解析
- (4)防止拷貝
- (5)對象持久化
- 4. 餓漢VS懶漢
- 總結
前言
今天我們一起來學習常見的特殊類怎么設計,以及了解什么是單例~
一、 設計一個不能被拷貝的類
拷貝發生在兩種場景:拷貝構造函數和賦值運算符。因此,若要禁止拷貝,只需讓該類不能調用這兩個函數。
1. C++98 實現方式
- 方法:將拷貝構造函數和賦值運算符 僅聲明不定義,并設置為
private
。 - 原因:
- 私有化拷貝構造和賦值運算符,防止外部訪問。
- 僅聲明不定義,確保該函數不會被調用,即使成員函數內部嘗試拷貝也會報錯。
class CopyBan {
private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);
};
2. C++11 實現方式
- C++11 提供
= delete
語法,顯式刪除默認拷貝構造和賦值運算符。
class CopyBan {
public:CopyBan(const CopyBan&) = delete;CopyBan& operator=(const CopyBan&) = delete;
};
二、設計一個只能在堆上創建對象的類
1. 方法一:析構函數私有,提供destory接口釋放資源
class HeapOnly
{
public:void Destroy(){delete this;}
private:~HeapOnly(){//...}
};int main()
{//HeapOnly hp1;//static HeapOnly hp2;HeapOnly* hp3 = new HeapOnly;//delete hp3;hp3->Destroy();return 0;
}
2. 方法二:構造函數私有
步驟:
- 構造函數私有
- 提供靜態成員函數創建對象
- 禁拷貝與賦值,防止利用拷貝創建棧上的對象
具體代碼和使用如下:
class HeapOnly
{
public:static HeapOnly* CreateObj(){return new HeapOnly;}
private:HeapOnly(){//...}HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;
};int main()
{//HeapOnly hp1;//static HeapOnly hp2;//HeapOnly* hp3 = new HeapOnly;HeapOnly* hp3 = HeapOnly::CreateObj();//HeapOnly copy(*hp3);return 0;
}
三、 設計一個只能在棧上創建對象的類
1. 實現方式
步驟:
- 類比只能在堆上創建對象的類,先將構造函數私有
- 提供靜態成員函數構造,不同的是只能在堆上創建對象的類new來創造,這里傳值返回
- 為了避免這種情況:StackOnly* copy = new StackOnly(s),這樣的拷貝方式創建堆上的對象,但是我們又不能禁拷貝和賦值,因此需要禁掉
operator new
,這樣來寫void* operator new(size_t) = delete
具體代碼如下:
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly st;return st;}
private:StackOnly(){//...}// 對一個類實現專屬operator newvoid* operator new(size_t size) = delete;
};int main()
{//StackOnly hp1;//static StackOnly hp2;//StackOnly* hp3 = new StackOnly;StackOnly hp3 = StackOnly::CreateObj();StackOnly copy(hp3);// new operator new + 構造// StackOnly* hp4 = new StackOnly(hp3);return 0;
}
四、設計一個不能被繼承的類
1. C++98 實現方式
- 方法:將構造函數
private
,防止繼承。
class NonInherit {
public:static NonInherit GetInstance() {return NonInherit();}private:NonInherit() {}
};
2. C++11 實現方式
- 方法:使用
final
關鍵字。
class A final {// 該類無法被繼承
};
五、設計一個只能創建一個對象(單例模式)
1. 單例模式介紹
- 保證系統中某個類只有一個實例。
- 提供全局訪問點。
- 應用場景:如全局配置管理。
2. 餓漢模式(Eager Singleton)
- 特點:程序啟動時即創建實例。(main函數之前就創建)
- 優點:線程安全。
- 缺點:可能導致啟動慢。
(1)餓漢模式的實現步驟
代碼:
namespace hungry
{class Singleton{public:// 2、提供獲取單例對象的接口函數, 返回靜態成員變量static Singleton& GetInstance(){return _sinst; }void func();void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}private:// 1、構造函數私有,防止外部直接創建對象Singleton() {}// 3、防拷貝:刪除拷貝構造和賦值運算符,避免創建多個實例Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;map<string, string> _dict;// 4、靜態成員變量,在程序啟動時就初始化static Singleton _sinst;};// 5、在類外部定義靜態實例,在main函數執行前已創建Singleton Singleton::_sinst;
}
(2)代碼解析
(1)構造函數私有化
Singleton() {}
- 目的是防止外部代碼通過
new
關鍵字創建對象,確保Singleton
類只能在GetInstance()
方法中創建實例。
(2)提供靜態方法 GetInstance()
static Singleton& GetInstance()
{return _sinst;
}
- 通過靜態方法返回單例對象的引用,確保所有地方訪問的都是同一個實例。
(3)防拷貝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
- 防止拷貝和賦值,避免創建多個
Singleton
實例。
(4)靜態成員變量 _sinst
static Singleton _sinst;
- 程序啟動時(
main()
執行前)就創建該實例,無論是否真的需要。
(3)餓漢模式的優缺點
? 優點
-
線程安全
- 靜態成員
_sinst
在編譯時創建,天然是線程安全的,無需額外同步措施(如mutex
)。
- 靜態成員
-
實現簡單
- 不需要加鎖,避免了懶漢模式的
double-check
加鎖復雜性。
- 不需要加鎖,避免了懶漢模式的
-
訪問速度快
- 由于實例在程序啟動時就已經創建,訪問時無延遲,直接返回。
? 缺點
-
浪費資源
- 如果該單例對象初始化內容很多,而程序運行期間根本沒用到,就會浪費資源,降低程序啟動速度。
-
難以控制對象創建順序
- 如果多個單例對象存在依賴關系(如 A 依賴 B),可能會導致未定義行為。
- 例如:
class A {static A a_instance;B b; // A 依賴 B };class B {static B b_instance;A a; // B 依賴 A };
- 由于
_sinst
在編譯期靜態初始化,兩個類的創建順序是由編譯器決定的,可能會出現 A 還未初始化,但 B 已經嘗試訪問 A 的問題。
- 由于
3. 懶漢模式(Lazy Singleton)
- 特點:第一次使用時創建實例。
- 優點:啟動快,資源按需分配。
- 缺點:線程不安全,需要加鎖。
懶漢模式(Lazy Singleton)的實現思路解析
懶漢模式是一種 單例模式(Singleton Pattern)的實現方式,其特點是 延遲創建實例,即第一次使用時才創建對象,而不是程序啟動時就初始化(像餓漢模式那樣)。
(1)為什么使用懶漢模式?
懶漢模式的主要優點是延遲加載,適用于 對象創建成本較高、但并不是一定會用到的情況,比如:
- 數據庫連接
- 日志管理
- 需要動態管理生命周期的單例(如緩存數據)
(2)代碼解析
完整代碼:
namespace lazy
{class Singleton{public:// 2、提供獲取單例對象的接口函數static Singleton& GetInstance(){if (_psinst == nullptr){// 第一次調用 GetInstance 時創建單例對象_psinst = new Singleton;}return *_psinst;}// 釋放單例對象(用于手動釋放或持久化數據)static void DelInstance(){if (_psinst){delete _psinst;_psinst = nullptr;}}void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}// 3、GC(垃圾回收)類,在程序結束時自動釋放 Singletonclass GC{public:~GC(){lazy::Singleton::DelInstance();}};private:// 1、構造函數私有,防止外部直接創建對象Singleton(){// ...}~Singleton(){cout << "~Singleton()" << endl;// map數據寫到文件中(持久化)FILE* fin = fopen("map.txt", "w");for (auto& e : _dict){fputs(e.first.c_str(), fin);fputs(":", fin);fputs(e.second.c_str(), fin);fputs("\n", fin);}fclose(fin);}// 4、防拷貝Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;map<string, string> _dict;static Singleton* _psinst; // 指向單例對象的指針static GC _gc; // 靜態 GC 對象,自動釋放 Singleton};// 5、靜態成員變量初始化Singleton* Singleton::_psinst = nullptr; // 初始化單例指針為空Singleton::GC Singleton::_gc; // 在程序退出時自動釋放 Singleton
}int main()
{cout << &lazy::Singleton::GetInstance() << endl;cout << &lazy::Singleton::GetInstance() << endl;cout << &lazy::Singleton::GetInstance() << endl;// 添加數據lazy::Singleton::GetInstance().Add({ "xxx", "111" });lazy::Singleton::GetInstance().Add({ "yyy", "222" });lazy::Singleton::GetInstance().Add({ "zzz", "333" });lazy::Singleton::GetInstance().Add({ "abc", "333" });// 打印數據lazy::Singleton::GetInstance().Print();// 修改數據lazy::Singleton::GetInstance().Add({ "abc", "444" });lazy::Singleton::GetInstance().Print();// 不手動調用 DelInstance,程序結束時 GC 自動釋放return 0;
}
(1)懶加載(Lazy Initialization)
static Singleton& GetInstance()
{if (_psinst == nullptr){_psinst = new Singleton;}return *_psinst;
}
特點:
- _psinst 指針初始化為
nullptr
,意味著程序啟動時不會創建實例。 - 首次調用
GetInstance()
時才創建Singleton
實例。 - 之后每次調用
GetInstance()
,返回的都是同一個對象。
(2)手動釋放單例
一般來說,單例是不需要去釋放的,
特殊場景:1、中途需要顯示釋放 2、程序結束時,需要做一些特殊動作(如持久化)(GC)
static void DelInstance()
{if (_psinst){delete _psinst;_psinst = nullptr;}
}
作用:
- 由于
Singleton
對象是動態創建的,所以需要手動釋放。 DelInstance()
用于手動釋放對象,當程序需要手動控制資源釋放時可以調用。
(3)GC 機制(自動釋放單例)
class GC
{
public:~GC(){lazy::Singleton::DelInstance();}
};
工作原理:
GC
是Singleton
內部的一個嵌套類。static GC _gc;
是一個 靜態成員變量,它的 析構函數會在程序退出時被調用,從而自動釋放Singleton
實例。
? 為什么要加 GC
?
- 避免 內存泄漏,因為
Singleton
對象是new
出來的,程序退出時如果不手動delete
,就會發生泄漏。 - 確保
Singleton
在main()
結束時釋放,不會影響其他對象析構的順序。
(4)防止拷貝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
- 防止 拷貝構造 和 賦值運算符,保證單例模式不被破壞。
(5)對象持久化
~Singleton()
{cout << "~Singleton()" << endl;// map數據寫到文件中FILE* fin = fopen("map.txt", "w");for (auto& e : _dict){fputs(e.first.c_str(), fin);fputs(":", fin);fputs(e.second.c_str(), fin);fputs("\n", fin);}fclose(fin);
}
- 在
Singleton
的析構函數中,把_dict
數據寫入文件,確保程序退出時數據不會丟失。
4. 餓漢VS懶漢
方式 | 線程安全 | 訪問速度 | 資源消耗 | 適用場景 |
---|---|---|---|---|
餓漢模式 | ? 安全 | ? 快 | ? 可能浪費 | 頻繁使用的單例對象(如日志、配置管理) |
懶漢模式 | ? 需加鎖 | ? 訪問有延遲 | ? 只在需要時創建 | 大量占用資源但不一定用到的單例 |
總結
到這里就結束啦~
謝謝大家,希望對您有所幫助~