总结C++单例模式
單例模式介紹
單例模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
單例模式有三個關鍵點:
1、單例類只能有一個實例。
為此,單例類只能提供私有的構造函數,即保證不能隨意創建該類的實例。
2、單例類必須自己創建自己的唯一實例。
因為構造函數是私有的,其他對象不能創建單例類的實例,只能是單例類自己來創建。
3、單例類必須給所有其他對象提供這一實例。
外界需要獲取并使用這個單例類的實例,但是由于該類的構造函數是私有的,外界無法通過new去獲取它的實例,那么就必須提供一個靜態的公有方法,該方法創建或者獲取它本身的靜態私有對象并返回。
單例類主要解決了一個全局使用的類的頻繁的創建與銷毀。
所以單例模式有以下幾個優點: 1、在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷毀實例;2、避免對資源的多重占用。
那單例模式有一個不好的地方就是,單例類沒有接口,不能繼承。
單例模式使用場景
單例模式就是一個類只能被實例化一次 ,也就是只能有一個實例化的對象的類。減少每次都new對象的開銷,節省系統資源,同時也保證了訪問對象的唯一實例。常用于如下場景:
1.頻繁實例化然后銷毀的對象。
2.創建對象太耗時,消耗太多資源,但又經常用到。
單例模式分為懶漢式和餓漢式兩種。
懶漢式
懶漢式:故名思義,懶漢很懶只有餓了才會去找吃的。也就是說,只有在需要使用的時候才會去實例化。
線程不安全的懶漢式
代碼:
#include <iostream> #include <thread> #include <mutex>using namespace std;//常規的懶漢式單例模式,存在線程安全問題 class Singleton2 { public:static Singleton2* getInstance() {if (instance == NULL) {instance = new Singleton2();}return instance;}void test() {cout << "this is Singleton2..." << endl;}private:Singleton2() { cout << "Singleton2構造函數" << endl; }; //構造函數私有化Singleton2(const Singleton2&) {} //拷貝構造函數私有化Singleton2& operator=(const Singleton2&) {} //賦值運算符私有化static Singleton2* instance; //靜態成員變量//類中嵌套類,用于自動回收資源class GC {public:~GC() {if (instance != NULL) {delete instance;instance = NULL;cout << "GC2自動回收..." << endl;}cout << "GC2..." << endl;}};static GC gc; }; //靜態成員變量,類內定義,類外初始化 Singleton2* Singleton2::instance = NULL; Singleton2::GC Singleton2::gc;int main() {Singleton2::getInstance()->test();return 0; }上面的代碼是線程不安全的單例模式。因為,如果兩個線程同時獲取單例類的實例,都發現實例不存在,因此都會進行實例化,就會產生兩個實例都要賦值給instance。
這里的線程安全指的是:一個線程在初始化某個變量的時候,其他線程執行到這個變量的初始化的時候,就會掛起。就是說只有一個線程會執行該變量的初始化
單例類中嵌套回收的類的作用是:不用再手動調用析構函數了。程序退出release對象析構時,單例的指針也會隨著析構。
結果:
為了解決上述問題,設計出線程安全的懶漢式單例模式,需要考慮加鎖。當然也可以不加鎖,使用靜態局部變量。
線程安全的懶漢式有兩種實現方式,一種是使用靜態局部變量,另一種是加鎖。
使用靜態局部變量(C++11)
由于C++11中規定靜態成員變量就是線程安全的,所以這種寫法不存在線程安全問題。
原理就是函數的局部靜態變量生命周期隨著進程結束而結束。
結果:
加鎖的懶漢式
#include <iostream> #include <thread> #include <mutex>using namespace std;//加鎖,解決常規的懶漢式單例模式,存在線程安全的問題 class Singleton3 { public:static Singleton3* getinstance() {//兩個NULL可以提高獲取實例效率if (instance == NULL) {mtx.lock();if (instance == NULL) {instance = new Singleton3();}mtx.unlock();}return instance;}void test() {cout << "this is Singleton3..." << endl;}private:Singleton3() { cout << "Singleton3構造函數" << endl; }Singleton3(const Singleton3&) {}Singleton3& operator=(const Singleton3&) {}static Singleton3* instance;static mutex mtx; //鎖//類中嵌套類,用于自動回收資源class GC {public:~GC() {if (instance != NULL) {delete instance;instance = NULL;cout << "GC3自動回收..." << endl;}cout << "GC3..." << endl;}};static GC gc; }; Singleton3* Singleton3::instance = NULL; mutex Singleton3::mtx; Singleton3::GC Singleton3::gc;int main() {Singleton3::getinstance()->test();return 0; }結果:
餓漢式
餓漢式:餓了肯定要饑不擇食。在單例類定義的時候就進行實例化。
餓漢式沒有線程安全問題。
代碼:
結果:
c++單例模式為什么不在析構函數中釋放靜態的單例對象
1、單例中的 new 的對象需要delete釋放。
2、delete釋放對象的時候才會調用對象的析構函數。
3、如果在析構函數里調用delete,那么程序結束時,根本進不去析構函數,怎么會delete。
4、如果程序結束能自動析構,那么就會造成一個析構的循壞,所以new對應于delete。
經過驗證,如果不手動delete對象,符合第3條,因為只有進行了delete的時候才會執行析構函數,但是delete是在析構函數中執行的,不在外部手動delete,則不會執行析構函數,所以在析構函數中釋放對象也沒有用。
但是,如果手動delete對象,將會出現死循環,也就是出現了第4條的情況。因為手動delete會調用析構函數,而在析構函數中,又使用了delete靜態的單例對象,進而又會調用析構函數,這樣就會一直循環下去,直到棧溢出。
綜上所述,不可以在析構函數中釋放靜態的單例對象。可以在類中嵌套一個回收的類,用于釋放單例的對象。
如果不在析構函數中釋放靜態的單例對象,而在外部手動使用delete,會釋放單例的對象嗎?
答案:不會
例如如下代碼:
結果:
從結果中可以看出,把Singleton2::getInstance()賦值給S2之后,S2就指向了單例對象的那塊內存,但是
即使把S2 delete掉之后,單例對象還是存在的。這有點像與智能指針,總之,無法通過外部的delete刪除單例對象,所以只能在單例類內部使用嵌套類來刪除單例對象,防止內存泄漏。
參考鏈接:
C++ 單例模式 - 泣血 - 博客園 (cnblogs.com)
C++實現單例模式_Elena-N的博客-CSDN博客_單例模式c++實現
【設計模式】單例模式,嵌套類實現釋放對象內存 - 走看看 (zoukankan.com)
C++單例模式為何要實例化一個對象不全部使用static_C 語言_腳本之家 (jb51.net)
C++單例模式 正確的資源回收方式_ice_ly000的博客-CSDN博客
總結
- 上一篇: vi使用手册(zt)
- 下一篇: win8虚拟机_VMware 11.1.