目錄
一、單例模式
單例模式的三個要點
針對上述三要點的解決方案
常用的兩類單例模式
?二、懶漢模式實現(xiàn)
1.基本實現(xiàn)
2.鎖+靜態(tài)成員析構(gòu)單例
3.雙層檢查鎖定優(yōu)化
4.雙層檢查鎖定+智能指針
三、餓漢模式實現(xiàn)
1.基礎(chǔ)實現(xiàn)
2.嵌套內(nèi)部類解決內(nèi)存泄漏
3.智能指針解決內(nèi)存泄漏?
一、單例模式
單例模式(Singleton Pattern)是 一種屬于創(chuàng)建型設(shè)計模式,它提供了一種創(chuàng)建對象的最佳方式。
這種模式涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。(即它確保一個類只有一個實例,并提供了一個全局訪問點來訪問該實例。)
單例模式的三個要點
- 1、單例類只能有一個實例。
- 2、單例類必須自己創(chuàng)建自己的唯一實例。
- 3、單例類必須給所有其他對象提供這一實例。
針對上述三要點的解決方案
1)私有化構(gòu)造函數(shù):這樣外界就無法自由地創(chuàng)建類對象,進(jìn)而阻止了多個實例的產(chǎn)生。
2)類定義中含有該類的唯一靜態(tài)私有對象:靜態(tài)變量存放在全局存儲區(qū),且是唯一的,供所有對象使用。
3)用公有的靜態(tài)函數(shù)來獲取該實例:提供了訪問接口。
常用的兩類單例模式
1)懶漢模式:在使用類對象(單例實例)時才會去創(chuàng)建它,不然就不創(chuàng)建。
2)餓漢模式:單例實例在類裝載時構(gòu)建,有可能全局都沒使用過,但它占用了空間,就像等著發(fā)救濟(jì)糧的餓漢提前排好隊等吃的一樣。
?二、懶漢模式實現(xiàn)
1.基本實現(xiàn)
//singleton.h
#pragma once
#include <iostream>
using namespace std;
class Singleton
{
public:
//公共接口獲取唯一實例
static Singleton* getInstance()
{
if (m_instance == nullptr)
{
cout << "創(chuàng)建實例" << endl;
m_instance = new Singleton;
}
return m_instance;
}
private:
//構(gòu)造私有
Singleton()
{
cout << "調(diào)用構(gòu)造函數(shù)" << endl;
}
//Singleton()=default;
~Singleton()
{
cout << "調(diào)用析構(gòu)函數(shù)" << endl;
}
//~Singleton() = default;
//禁用拷貝構(gòu)造和賦值運算符(=delete 為C++11新標(biāo)準(zhǔn))
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
//靜態(tài)私有對象
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr; //初始化
#include "singleton.h"
int main()
{
Singleton* instance1 = Singleton::getInstance();
Singleton* instance2 = Singleton::getInstance();
return 0;
}
?執(zhí)行結(jié)果:
由上述結(jié)果可知,的確只創(chuàng)建了一個實例。
但同時暴露了兩個問題:①線程安全;②內(nèi)存泄漏
①線程安全:在多線程場景下,可能多個線程進(jìn)行new操作,需要加鎖進(jìn)行限制,保證只進(jìn)行一次new操作。
#include "singleton.h"
int main()
{
thread t1([] {Singleton* s1 = Singleton::getInstance();});
thread t2([] {Singleton* s2 = Singleton::getInstance();});
t1.join();
t2.join();
return 0;
}
?
?②內(nèi)存泄漏:new在堆上的資源在程序結(jié)束時,需要通過delete進(jìn)行釋放。上面并沒有調(diào)用析構(gòu)函數(shù)執(zhí)行delete操作。
2.鎖+靜態(tài)成員析構(gòu)單例
#include <iostream>
#include <mutex>
using namespace std;
//鎖+靜態(tài)成員析構(gòu)單例
class Singleton
{
public:
static Singleton* getInstance()
{
m_mutex.lock();//上鎖
if (m_instance == nullptr)
{
cout << "創(chuàng)建實例" << endl;
m_instance = new Singleton;
}
m_mutex.unlock();//解鎖
return m_instance;
}
private:
Singleton()
{
cout << "調(diào)用構(gòu)造函數(shù)" << endl;
}
//Singleton()=default;
~Singleton()
{
cout << "調(diào)用析構(gòu)函數(shù)" << endl;
}
//~Singleton() = default;
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
class FreeInstace
{
public:
FreeInstace()=default;
~FreeInstace()
{
if (Singleton::m_instance != nullptr)
{
delete Singleton::m_instance;
cout << "單例銷毀" << endl;
}
}
};
private:
//靜態(tài)私有對象
static Singleton* m_instance;
static FreeInstace m_freeinstance;
static mutex m_mutex;
};
Singleton* Singleton::m_instance = nullptr; //初始化
Singleton::FreeInstace Singleton::m_freeinstance;
mutex Singleton::m_mutex;
該方案的缺點在于對Singleton的每次訪問都需要獲取一個鎖,鎖導(dǎo)致速度慢,效率低。但實際上,我們只需要一個鎖,初始化m_instance時(即確定m_instance指向時),這應(yīng)該只在第一次調(diào)用實例時發(fā)生。如果在程序運行的過程中調(diào)用了n次instance,則只在第一次調(diào)用時需要鎖。當(dāng)你知道n - 1個鎖是不必要的,為什么還要為n個鎖的獲取買單呢?
3.雙層檢查鎖定優(yōu)化
static Singleton* getInstance()
{
if (m_instance == nullptr)
{
m_mutex.lock();//上鎖
if (m_instance == nullptr)
{
cout << "創(chuàng)建實例" << endl;
m_instance = new Singleton;
}
m_mutex.unlock();//解鎖
}
return m_instance;
}
雙層檢查鎖定的關(guān)鍵是觀察到大多數(shù)對instance的調(diào)用將看到m_instance是非空的,因此不會嘗試初始化它。因此,它嘗試獲取鎖之前測試m_instance是否為空。只有當(dāng)測試成功(即m_instance尚未初始化)時,才會獲得鎖,然后再次執(zhí)行測試以確保m_instance仍然為空(因此稱為雙重檢查鎖定)。第二個測試是必要的,因為,正如上面描述的情況在m_instance第一次被測試到獲得鎖的時間之間,有可能發(fā)生另一個線程初始化m_instance的情況。
使用雙層檢查鎖定將已經(jīng)初始化的對象的直接返回。可以使代碼性能會大大加快。但它們沒有考慮到一個更基本的問題,即確保在雙層檢查鎖定期間執(zhí)行的機器指令以可接受的順序執(zhí)行。
m_instance = new Singleton;
這個語句導(dǎo)致三件事發(fā)生:
步驟1:分配內(nèi)存來保存Singleton對象。
步驟2:在分配的內(nèi)存中構(gòu)造一個單例對象。
步驟3:使m_instance 指向已分配的內(nèi)存。
最重要的是觀察到編譯器不受約束,會按照這個順序執(zhí)行這些步驟!特別是,編譯器有時允許交換步驟2和步驟3。所以可能導(dǎo)致訪問到未初始化的對象的引用。
解決方案:可以參考如下鏈接C++完美單例模式 - 簡書
4.雙層檢查鎖定+智能指針
針對內(nèi)存泄漏問題,除了可以方法2介紹的使用靜態(tài)成員在程序結(jié)束時,銷毀成員是調(diào)用析構(gòu)進(jìn)行delete,還可以使用智能指針,頭文件引用<memory>。
class Singleton
{
public:
static shared_ptr<Singleton> getInstance()
{
if (m_instance == nullptr)
{
m_mutex.lock();//上鎖
if (m_instance == nullptr)
{
cout << "創(chuàng)建實例" << endl;
m_instance.reset( new Singleton(), destoryInstance);
}
m_mutex.unlock();//解鎖
}
return m_instance;
}
static void destoryInstance(Singleton* x)
{
cout << "自定義釋放實例" << endl;
delete x;
}
private:
Singleton()
{
cout << "調(diào)用構(gòu)造函數(shù)" << endl;
}
//Singleton()=default;
~Singleton()
{
cout << "調(diào)用析構(gòu)函數(shù)" << endl;
}
//~Singleton() = default;
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
//靜態(tài)私有對象
static shared_ptr<Singleton> m_instance;
static mutex m_mutex;
};
shared_ptr<Singleton> Singleton::m_instance = nullptr; //初始化
mutex Singleton::m_mutex;
應(yīng)用智能指針后,在程序結(jié)束時,它自動進(jìn)行資源的釋放,解決了內(nèi)存泄漏的問題。
三、餓漢模式實現(xiàn)
餓漢和懶漢的差別就在于,餓漢提前進(jìn)行了創(chuàng)建。
1.基礎(chǔ)實現(xiàn)
class Singleton
{
public:
//公共接口獲取唯一實例
static Singleton* getInstance()
{
return m_instance;
}
private:
//構(gòu)造私有
Singleton()
{
cout << "調(diào)用構(gòu)造函數(shù)" << endl;
}
//Singleton()=default;
~Singleton()
{
cout << "調(diào)用析構(gòu)函數(shù)" << endl;
}
//~Singleton() = default;
//禁用拷貝構(gòu)造和賦值運算符(=delete 為C++11新標(biāo)準(zhǔn))
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
//靜態(tài)私有對象
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = new Singleton; //初始化
所以main還沒開始,實例就已經(jīng)構(gòu)建完畢。獲取實例的函數(shù)也不需要進(jìn)行判空操作,因此也就不用雙重檢測鎖來保證線程安全了,它本身已經(jīng)是線程安全狀態(tài)了。文章來源:http://www.zghlxwxcb.cn/news/detail-591470.html
但是內(nèi)存泄漏的問題還是要解決的。文章來源地址http://www.zghlxwxcb.cn/news/detail-591470.html
2.嵌套內(nèi)部類解決內(nèi)存泄漏
class Singleton
{
public:
//公共接口獲取唯一實例
static Singleton* getInstance()
{
return m_instance;
}
private:
//構(gòu)造私有
Singleton()
{
cout << "調(diào)用構(gòu)造函數(shù)" << endl;
}
//Singleton()=default;
~Singleton()
{
cout << "調(diào)用析構(gòu)函數(shù)" << endl;
}
//~Singleton() = default;
//禁用拷貝構(gòu)造和賦值運算符(=delete 為C++11新標(biāo)準(zhǔn))
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
class FreeInstace
{
public:
FreeInstace()=default;
~FreeInstace()
{
if (Singleton::m_instance != nullptr)
{
delete Singleton::m_instance;
cout << "單例銷毀" << endl;
}
}
};
private:
//靜態(tài)私有對象
static Singleton* m_instance;
static FreeInstace m_freeinstance;
};
Singleton* Singleton::m_instance = new Singleton; //初始化
Singleton::FreeInstace Singleton::m_freeinstance;
3.智能指針解決內(nèi)存泄漏?
class Singleton
{
public:
//公共接口獲取唯一實例
static shared_ptr<Singleton> getInstance()
{
return m_instance;
}
static void destoryInstance(Singleton* x) {
cout << "自定義釋放實例" << endl;
delete x;
}
private:
//構(gòu)造私有
Singleton()
{
cout << "調(diào)用構(gòu)造函數(shù)" << endl;
}
//Singleton()=default;
~Singleton()
{
cout << "調(diào)用析構(gòu)函數(shù)" << endl;
}
//~Singleton() = default;
//禁用拷貝構(gòu)造和賦值運算符(=delete 為C++11新標(biāo)準(zhǔn))
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
//靜態(tài)私有對象
static shared_ptr<Singleton> m_instance;
};
shared_ptr<Singleton> Singleton::m_instance ( new Singleton, destoryInstance); //初始化
到了這里,關(guān)于【C++】設(shè)計模式-單例模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!