C++深入詳解單例模式與特殊類設(shè)計的實(shí)現(xiàn)
單例模式
什么是單例模式
單例模式(Singleton),保證一個類僅有一個實(shí)例,并提供一個訪問它的全局訪問點(diǎn)。–大話設(shè)計模式
應(yīng)用場景
保證一個類只有一個實(shí)例
- 如Windows下的任務(wù)管理器,回收站等。
- 日志管理,計數(shù)器等。
簡而言之,你需要唯一實(shí)例時就可以考慮單例模式。這樣它可以嚴(yán)格地控制客戶怎樣訪問即何時訪問它,即對唯一實(shí)例的受控訪問。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 減少內(nèi)存開銷,因?yàn)樵谙到y(tǒng)中只有一個實(shí)例。
- 避免了頻繁的創(chuàng)建和銷毀對象,提高了性能
- 避免對資源的多重占用,比如單例時多人只寫一個日志文件,如果有多個日志文件可能導(dǎo)致對相同的日志文件進(jìn)行寫操作
- 設(shè)置全局訪問點(diǎn)
缺點(diǎn)
- 職責(zé)過重,與單一職責(zé)存在沖突
- 不能繼承(構(gòu)造方法私有)
實(shí)現(xiàn)
單例模式有兩種實(shí)現(xiàn)模式,懶漢模式和餓漢模式。
注意單例模式的概念,大概就是唯一實(shí)例且有全局訪問點(diǎn),我們從這兩點(diǎn)入手。
唯一實(shí)例:構(gòu)造函數(shù)私有+防拷貝
拷貝構(gòu)造是一種構(gòu)造方式,所以需要防止,構(gòu)造函數(shù)私有不讓別人new。
全局訪問點(diǎn):給一個公共的接口
餓漢模式
簡單來說,把東西一開始就做好,需要的時候直接吃。
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton& GetInstance()
{
return _instance;
}
int GetRandom()//由于側(cè)重點(diǎn)不是隨機(jī)數(shù) 所以直接返回一個30
{
return 30;
}
private:
Singleton() {}//構(gòu)造函數(shù)私有
Singleton(Singleton&) = delete;//防拷貝,被=delete修飾表明這個函數(shù)被刪除,即可以只聲明不實(shí)現(xiàn),換言之禁用了該函數(shù)
Singleton& operator=(const Singleton&) = delete;//防拷貝
static Singleton _instance;
};
Singleton Singleton::_instance;//類外必須初始化,類內(nèi)只是聲明
int main()
{
//1.調(diào)用
cout<<Singleton::GetInstance().GetRandom()<<endl;
//2.
Singleton& s = Singleton::GetInstance();
cout<<s.GetRandom()<<endl;
return 0;
}通過防拷貝和構(gòu)造函數(shù)私有化之后下面的幾種辦法都失效了
Singleton s;//err Singleton s1(s);//err Singleton s1=s;//err
從上面我們可以看出餓漢模式的優(yōu)缺點(diǎn)了,有點(diǎn)顯而易見就是實(shí)現(xiàn)很簡單粗暴,缺點(diǎn)很明顯,類加載時單例對象就已經(jīng)生成了,即還沒有用就已經(jīng)加載出來了,比如這個資源很大,又在游戲啟動時加載,那就會造成游戲啟動很慢。且如果有多個單例對象啟動時實(shí)例化順序不確定(不同源文件類內(nèi)的單例對象實(shí)例化順序是不確定的,懶漢模式解決了這個問題,因?yàn)閼袧h模式的實(shí)例化在函數(shù)內(nèi)部,可以通過調(diào)用函數(shù)的順序來解決實(shí)例化的順序問題)。
類加載時靜態(tài)初始化解決了線程安全問題。
懶漢模式
什么時候需要就什么時候做飯,然后吃。
class Singleton
{
public:
static Singleton* GetInstance()
{
if (_instance == nullptr)
{
_mtx.lock();//double lock保證線程安全
if (_instance == nullptr)//必須再次檢查 不然可能另一個線程那已經(jīng)new完了,這邊又new違背了單例,且可能覆蓋數(shù)據(jù)。
{
_instance = new Singleton();
}
_mtx.unlock();
}
return _instance;
}
int GetRandom()
{
return 30;
}
class Clear//資源回收的內(nèi)部類,必須是公有的,不然外部聲明報錯
{
public:
~Clear()
{
cout << "釋放資源" << endl;
delete _instance;
}
};
private:
static Clear _cle;
Singleton() {}
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* _instance;
static mutex _mtx;//線程安全
};
Singleton* Singleton::_instance = nullptr;
mutex Singleton::_mtx;
Singleton::Clear _cle;
int main()
{
//1.
cout<<Singleton::GetInstance()->GetRandom()<<endl;
//2/
static Singleton* s = Singleton::GetInstance();
cout << s->GetRandom() << endl;
//不能用左值引用接收的原因 返回值是右值 得用右值引用接收
//為什么函數(shù)返回值是右值 因?yàn)榉祷刂凳墙柚R時變量返回的拿不到地址
//如果返回值是左值引用就能用左值引用接收
return 0;
}懶漢模式的優(yōu)缺點(diǎn)也很明顯,優(yōu)點(diǎn)是什么時候第一次用就什么時候?qū)嵗送饪梢酝ㄟ^調(diào)用函數(shù)解決多個單例對象實(shí)例化的順序問題,缺點(diǎn)就是寫起來復(fù)雜,要考慮線程安全和內(nèi)存泄露方面的問題。懶漢采用的是指針也更好回收資源(餓漢采用的是對象)
懶漢可能因?yàn)槎嗑€程丟數(shù)據(jù),線程加鎖保證在多線程環(huán)境下一定只有一個線程去new對象,只創(chuàng)建出一個單例對象,加鎖可能導(dǎo)致頻繁切換上下文,double lock解決。
特殊類設(shè)計
我們一般會通過構(gòu)造函數(shù),拷貝構(gòu)造和賦值重載創(chuàng)建對象,現(xiàn)在把這幾種方法全禁用了,然后自己再寫一個外部可調(diào)用的接口來自定義類的創(chuàng)建方式。
當(dāng)然也有別的辦法,這種經(jīng)??梢宰鳛橐环N通解思路。
下面的禁用采用C++11的delete關(guān)鍵字實(shí)現(xiàn),作用是禁止編譯器生成默認(rèn)的函數(shù)版本,即有聲明但無實(shí)現(xiàn)。
設(shè)計一個類只能在堆上創(chuàng)建對象
簡單來說只能通過new來創(chuàng)建對象。
方法一
構(gòu)造方法禁用,然后給一個接口讓外部調(diào)用。
構(gòu)造方法的禁用=構(gòu)造函數(shù)私有+禁用拷貝構(gòu)造+禁用賦值重載
class HeapOnly
{
public:
static HeapOnly* GetInstance()
{
return new HeapOnly;
}
void Test()
{
cout << "I am Test" << endl;
}
private:
HeapOnly() {}
HeapOnly(HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
int main()
{
HeapOnly* ho = HeapOnly::GetInstance();
ho->Test();
delete ho;
return 0;
}

方法二
析構(gòu)函數(shù)私有
對象建立在棧上,是由編譯器分配空間的,編譯器管理對象的生命周期,對象使用完后編譯器會檢查這個對象所有的非靜態(tài)函數(shù),包括析構(gòu)函數(shù),當(dāng)編譯器發(fā)現(xiàn)析構(gòu)函數(shù)不能訪問后就不能回收這塊空間,所以編譯器無法為其分配空間,編譯器檢查到這種情況也會報錯。
析構(gòu)函數(shù)私有的方法不建議使用,因?yàn)樵陬愅鉄o法使用delete釋放空間,容易造成內(nèi)存泄漏。
class HeapOnly
{
public:
void Test()
{
cout << "I am Test" << endl;
}
private:
~HeapOnly();
};
int main()
{
//HeapOnly ho_stack;//err
HeapOnly* ho = new HeapOnly;
ho->Test();
return 0;
}

只能在棧上創(chuàng)建對象
方法一
不能使用new --> 重載operator new即可。
這種存在一個缺陷,就是仍然可以在靜態(tài)區(qū)創(chuàng)建對象

方法二
將構(gòu)造函數(shù)設(shè)為私有再自定義一個接口
這里不用禁用構(gòu)造函數(shù),拷貝構(gòu)造,賦值重載,因?yàn)閷τ谙旅娴膱鼍皝碚f,匿名對象的拷貝構(gòu)造更符合場景,編譯器會選拷貝構(gòu)造來進(jìn)行構(gòu)造,所以如果我們禁用了拷貝構(gòu)造會報錯,因?yàn)槲覀兊膁elete關(guān)鍵字是有聲明無實(shí)現(xiàn),并不是真的把這個函數(shù)連帶聲明刪除了。所以StackOnly()一看有我們自己寫的拷貝構(gòu)造的聲明就會去匹配這個拷貝構(gòu)造,我們自己寫的拷貝構(gòu)造又沒有實(shí)現(xiàn)拷貝功能就會報錯。
理解這個和編譯鏈接知識有關(guān),編譯器看到聲明就去匹配了,而不是一定要看到函數(shù)實(shí)現(xiàn)才匹配,鏈接時候才會去找實(shí)現(xiàn),當(dāng)發(fā)現(xiàn)有聲明無實(shí)現(xiàn)時就很容易導(dǎo)致鏈接錯誤。

一個類不能被繼承
父類構(gòu)造函數(shù)私有即可,構(gòu)造時先構(gòu)造父類再構(gòu)造子類,父類構(gòu)造不出不能繼承。
最后
關(guān)于單例模式,可以說是只能創(chuàng)建一個對象的實(shí)現(xiàn)。
還有個小問題,為什么不用全局變量代替單例模式,全局定義一個唯一的變量不就行了,合理吧[doge],理論上可以,但是非常不建議,可以自行查找資料"為什么不建議使用全局變量"。
全局變量的使用可能帶來很多問題,而且很容易造成鏈接、重定義等錯誤,如果多人協(xié)作時有人再給這個變量起個別名,那維護(hù)代碼時代價就太大了,這還只是全局變量的一個小缺點(diǎn)。
牛客代碼規(guī)范評分里也不建議用全局變量,當(dāng)然寫題時代碼沒那么長,幾個全局變量倒時不打緊。
.h不能包含定義,不然多個cpp去包含就會有鏈接錯誤,盡量把定義和聲明分離開來
【82】【Cherno C++】【中字】C++的單例模式_嗶哩嗶哩_bilibili
到此這篇關(guān)于C++深入詳解單例模式與特殊類設(shè)計的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt中圖片旋轉(zhuǎn)縮放操作的實(shí)現(xiàn)
本文主要介紹了Qt中圖片旋轉(zhuǎn)縮放操作的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01
C++ 11實(shí)現(xiàn)檢查是否存在特定的成員函數(shù)
C++11/14相比以往的C++98/03在很多方面做了簡化和增強(qiáng),尤其是在泛型編程方面,讓C++的泛型編程的威力變得更加強(qiáng)大,下面這篇文章主要介紹了利用C++ 11實(shí)現(xiàn)檢查是否存在特定成員函數(shù)的相關(guān)資料,需要的朋友可以參考下。2017-02-02
Visual Studio 2019 Professional 激活方法詳解
這篇文章主要介紹了Visual Studio 2019 Professional 激活方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05
Visual Studio Code運(yùn)行程序時輸出中文成亂碼問題及解決方法
這篇文章主要介紹了解決Visual Studio Code運(yùn)行程序時輸出中文成亂碼問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03

