每一個(gè)不曾起舞的日子都是對(duì)生命的辜負(fù)
- 掌握常見特殊類的設(shè)計(jì)方式
一.設(shè)計(jì)一個(gè)類,不能被拷貝
拷貝只會(huì)放生在兩個(gè)場(chǎng)景中:拷貝構(gòu)造函數(shù)以及賦值運(yùn)算符重載,因此想要讓一個(gè)類禁止拷貝,只需讓該類不能調(diào)用拷貝構(gòu)造函數(shù)以及賦值運(yùn)算符重載即可。
-
C++98
將拷貝構(gòu)造函數(shù)與賦值運(yùn)算符重載只聲明不定義,并且將其訪問(wèn)權(quán)限設(shè)置為私有即可。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
- 設(shè)置成私有:如果只聲明沒(méi)有設(shè)置成private,用戶自己如果在類外定義了,就可以不能禁止拷貝了
- 只聲明不定義:不定義是因?yàn)樵摵瘮?shù)根本不會(huì)調(diào)用,定義了其實(shí)也沒(méi)有什么意義,不寫反而還簡(jiǎn)單,而且如果定義了就不會(huì)防止成員函數(shù)內(nèi)部拷貝了。
-
C++11
C++11擴(kuò)展delete的用法,delete除了釋放new申請(qǐng)的資源外,如果在默認(rèn)成員函數(shù)后跟上=delete,表示讓編譯器刪除掉該默認(rèn)成員函數(shù)。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
二.設(shè)計(jì)一個(gè)類,只能在堆上創(chuàng)建對(duì)象
1. 普通類的創(chuàng)建對(duì)象
普通的類,可以在三種位置上創(chuàng)建對(duì)象:
- 棧
- 堆
- 靜態(tài)區(qū)
#include<iostream>
using namespace std;
class HeapOnly
{};
int main()
{
HeapOnly hp1;//棧
HeapOnly* php2 = new HeapOnly;//堆
static HeapOnly hp3;//靜態(tài)區(qū)
return 0;
}
2.只能在堆上創(chuàng)建對(duì)象的類
要想只能在堆上創(chuàng)建對(duì)象,那一定需要在構(gòu)造函數(shù)上動(dòng)手腳,因?yàn)闃?gòu)造函數(shù)默認(rèn)在棧上創(chuàng)建對(duì)象。
實(shí)現(xiàn)方式:
- 將類的構(gòu)造函數(shù)私有,拷貝構(gòu)造聲明成私有。防止別人調(diào)用拷貝在棧上生成對(duì)象。
- 提供一個(gè)靜態(tài)的成員函數(shù),在該靜態(tài)成員函數(shù)中完成堆對(duì)象的創(chuàng)建。
#include<iostream>
using namespace std;
class HeapOnly
{
public:
static HeapOnly* CreateObject()
{
return new HeapOnly;
}
private:
HeapOnly()
{}
};
int main()
{
HeapOnly* php = HeapOnly::CreateObject();
return 0;
}
為什么要加上static?
如果CreateObject不加上static,那么在調(diào)用該方法就需要在存在對(duì)象的基礎(chǔ)上才能使用該方法,而該對(duì)象默認(rèn)一定會(huì)用構(gòu)造函數(shù),但是構(gòu)造函數(shù)已經(jīng)私有化,這就是一個(gè)先有雞還是先有蛋的問(wèn)題,因此一定要加上static。
但是就目前的情況,仍然可能在棧上開辟對(duì)象,首先友元一定是可以的。其次,拷貝構(gòu)造函數(shù)沒(méi)有顯示化調(diào)用會(huì)默認(rèn)生成,因此,如下方式仍可以在棧上創(chuàng)建對(duì)象:
int main()
{
HeapOnly* php2 = HeapOnly::CreateObject();
HeapOnly php3(*php2);//棧上創(chuàng)建對(duì)象
return 0;
}
所以,拷貝構(gòu)造函數(shù)同樣需要禁掉,才是只能在堆上創(chuàng)建的類:
#include<iostream>
using namespace std;
class HeapOnly
{
public:
static HeapOnly* CreateObject()
{
return new HeapOnly;
}
private:
HeapOnly() {}
HeapOnly(const HeapOnly&) = delete;
};
int main()
{
HeapOnly* php2 = HeapOnly::CreateObject();
return 0;
}
只在堆上創(chuàng)建類的第二種方式:析構(gòu)私有化
如果析構(gòu)私有化,那么直接創(chuàng)建對(duì)象會(huì)顯示沒(méi)有合適的構(gòu)造函數(shù),從而無(wú)法在棧上創(chuàng)建對(duì)象。
class HeapOnly
{
public:
HeapOnly()
{}
private:
~HeapOnly()
{}
HeapOnly(const HeapOnly&) = delete;
};
int main()
{
HeapOnly hp1;
return 0;
}
但此時(shí)可以在堆上創(chuàng)建,那么此時(shí)分為如下步驟:
- 析構(gòu)函數(shù)私有化
- 構(gòu)造函數(shù)public顯示調(diào)用
- 新增Destory方法,用來(lái)釋放堆空間
class HeapOnly
{
public:
HeapOnly()
{}
void Destory()
{
this->~HeapOnly();
}
private:
~HeapOnly()
{}
HeapOnly(const HeapOnly&) = delete;
};
int main()
{
HeapOnly* php1 = new HeapOnly;
php1->Destory();
return 0;
}
Destory對(duì)于static沒(méi)有要求,用不用static修飾完全是我們自己所決定的。
注:在vs2019中,上面的this必須顯示調(diào)用才沒(méi)有錯(cuò)誤。
三.設(shè)計(jì)一個(gè)類,只能在棧上創(chuàng)建對(duì)象
方法一:(同上)
- 將構(gòu)造函數(shù)私有化。
- 然后設(shè)計(jì)靜態(tài)方法創(chuàng)建對(duì)象返回即可。
//請(qǐng)?jiān)O(shè)計(jì)一個(gè)類,只能在棧上創(chuàng)建對(duì)象
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
private:
StackOnly()
{}
};
int main()
{
StackOnly so1 = StackOnly::CreateObj();
// 下面兩種靜態(tài)區(qū)和堆的位置都不能創(chuàng)建
//static StackOnly so2;
//StackOnly* pso3 = new StackOnly;
return 0;
}
實(shí)際上,這種方法也沒(méi)有徹底的封死,下面這種方式仍然可以在靜態(tài)區(qū)創(chuàng)建:
int main()
{
static StackOnly so2 = StackOnly::CreateObj();
return 0;
}
解決這個(gè)問(wèn)題的方式:
這里設(shè)計(jì)到的強(qiáng)制類型轉(zhuǎn)換,強(qiáng)制類型轉(zhuǎn)換中間會(huì)生成一個(gè)臨時(shí)對(duì)象,將這個(gè)臨時(shí)對(duì)象拷貝給需要定義的對(duì)象,若把拷貝構(gòu)造封住,那么不僅這個(gè)會(huì)報(bào)錯(cuò),前面的也會(huì)報(bào)錯(cuò),因?yàn)榍罢叩馁x值也是將返回的對(duì)象臨時(shí)拷貝:
因此,沒(méi)有什么很好的辦法去完全的封死。但硬要封死,即把拷貝構(gòu)造封住,那就不要用 = 獲取,而是直接調(diào)用,如下:
//請(qǐng)?jiān)O(shè)計(jì)一個(gè)類,只能在棧上創(chuàng)建對(duì)象
class StackOnly
{
public:
static StackOnly&& CreateObj()
{
return StackOnly();
}
void Print() const
{
cout << "StackOnly::Print()" << endl;
}
private:
StackOnly()
{}
StackOnly(const StackOnly&) = delete;
};
int main()
{
/*StackOnly so1 = StackOnly::CreateObj();
static StackOnly so2 = StackOnly::CreateObj();*/
StackOnly::CreateObj().Print();
const StackOnly& so4 = StackOnly::CreateObj();
so4.Print();
return 0;
}
目的就是防止拷貝。
ps,由于StackOnly()是局部對(duì)象,出了作用域被銷毀,因此采用右值引用才可以傳出。
方法二:封注operator new
和operator delete
class StackOnly
{
public:
StackOnly()
{}
static StackOnly CreateObj()
{
return StackOnly();
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
};
這種方式同樣可以,因?yàn)樵趎ew對(duì)象的過(guò)程中,一定會(huì)存在operator new的步驟。但是這種方法只能封住堆上的,卻無(wú)法封住靜態(tài)的。
所以最好的方式就是用方式一。
四.設(shè)計(jì)一個(gè)類,不能被繼承
- C++98方式
// C++98中構(gòu)造函數(shù)私有化,派生類中調(diào)不到基類的構(gòu)造函數(shù)。則無(wú)法繼承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
- C++11方法
final關(guān)鍵字,final修飾類,表示該類不能被繼承。
class A final
{
// ....
};
五.單例模式
- 單例模式:只能創(chuàng)建一個(gè)對(duì)象。
1.什么是設(shè)計(jì)模式?
設(shè)計(jì)模式是在軟件工程中經(jīng)過(guò)反復(fù)實(shí)踐證明的一套解決問(wèn)題的經(jīng)驗(yàn)總結(jié),用于解決常見的設(shè)計(jì)問(wèn)題。以下是一些常見的設(shè)計(jì)模式:
-
創(chuàng)建型模式(Creational Patterns):
- 工廠方法模式(Factory Method Pattern)
- 抽象工廠模式(Abstract Factory Pattern)
- 單例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
-
結(jié)構(gòu)型模式(Structural Patterns):
- 適配器模式(Adapter Pattern)
- 橋接模式(Bridge Pattern)
- 組合模式(Composite Pattern)
- 裝飾者模式(Decorator Pattern)
- 外觀模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
-
行為型模式(Behavioral Patterns):
- 觀察者模式(Observer Pattern)
- 狀態(tài)模式(State Pattern)
- 策略模式(Strategy Pattern)
- 命令模式(Command Pattern)
- 職責(zé)鏈模式(Chain of Responsibility Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 備忘錄模式(Memento Pattern)
- 訪問(wèn)者模式(Visitor Pattern)
- 模板方法模式(Template Method Pattern)
-
并發(fā)型模式(Concurrent Patterns):
- 信號(hào)量模式(Semaphore Pattern)
- 線程池模式(Thread Pool Pattern)
- 讀寫鎖模式(Read-Write Lock Pattern)
- 生產(chǎn)者消費(fèi)者模式(Producer-Consumer Pattern)
以上僅是一些常見的設(shè)計(jì)模式,實(shí)際上還有其他的設(shè)計(jì)模式。每個(gè)設(shè)計(jì)模式都有特定的應(yīng)用場(chǎng)景和解決問(wèn)題的方式。請(qǐng)注意,在使用設(shè)計(jì)模式時(shí),應(yīng)根據(jù)具體的需求和情況來(lái)選擇適當(dāng)?shù)脑O(shè)計(jì)模式。
比如迭代器模式,把復(fù)雜的東西給封裝好,使用時(shí)就可以避免接觸復(fù)雜的底層結(jié)構(gòu)。
比如配接器模式等等,也是這個(gè)意思。
使用設(shè)計(jì)模式的目的: 為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設(shè)計(jì)模式使代碼編寫真正工程化;設(shè)計(jì)模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣。
2.單例模式
一個(gè)類只能創(chuàng)建一個(gè)對(duì)象,即單例模式,該模式可以保證系統(tǒng)中該類只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn),該實(shí)例被所有程序模塊共享。比如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息存放在一個(gè)文件中,這些配置數(shù)據(jù)由一個(gè)單例對(duì)象統(tǒng)一讀取,然后服務(wù)進(jìn)程中的其他對(duì)象再通過(guò)這個(gè)單例對(duì)象獲取這些配置信息,這種方式簡(jiǎn)化了在復(fù)雜環(huán)境下的配置管理。
由于全局對(duì)象只能有一個(gè),換句話說(shuō)是獲取這個(gè)對(duì)象,那就需要對(duì)構(gòu)造函數(shù)進(jìn)行操作。
單例模式有兩種實(shí)現(xiàn)模式:餓漢模式、懶漢模式
餓漢模式:不管你將來(lái)用不用,程序啟動(dòng)時(shí)就創(chuàng)建一個(gè)唯一的實(shí)例對(duì)象。
餓漢模式的條件:main函數(shù)之前就初始化
設(shè)計(jì)餓漢模式的步驟:
- 將構(gòu)造函數(shù)設(shè)成private,以及封死拷貝構(gòu)造和重載賦值
- 定義成員變量,變量類型為
static 類型名
- 在類外初始化這個(gè)單例的對(duì)象
- 添加其它成員方法
//單例模式的類:全局只有一個(gè)唯一對(duì)象
// 餓漢模式(main函數(shù)之前初始化)
// 缺點(diǎn):1、單例對(duì)象初始化時(shí)對(duì)象太多,導(dǎo)致啟動(dòng)慢
// 2、多個(gè)單例類有初始化依賴關(guān)系,餓漢模式無(wú)法控制
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{
return _sins;
}
void Insert(string name, int salary)
{
_info[name] = salary;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ":" << kv.second << endl;
}
}
private:
InfoSingleton()
{}
InfoSingleton(const InfoSingleton&) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
map<string, int> _info;
// ...
private:
static InfoSingleton _sins;
};
InfoSingleton InfoSingleton::_sins;
int main()
{
InfoSingleton::GetInstance().Insert("張三", 10000);
InfoSingleton& infosl = InfoSingleton::GetInstance();
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
cout << endl;
InfoSingleton::GetInstance().Insert("張三", 18000);
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
return 0;
}
可見在調(diào)用時(shí)可以通過(guò)引用來(lái)簡(jiǎn)化代碼量。
餓漢模式的缺點(diǎn):
- 單例對(duì)象初始化數(shù)據(jù)太多,導(dǎo)致啟動(dòng)慢
- 多個(gè)單例類有初始化依賴關(guān)系,餓漢模式無(wú)法控制
假設(shè)有兩個(gè)單例類A和B,分別代表數(shù)據(jù)庫(kù)和文件系統(tǒng),要求先初始化A,再初始化B,并且B會(huì)依賴A,那么此時(shí)餓漢模式就無(wú)法控制順序。
如果這個(gè)單例對(duì)象在多線程高并發(fā)環(huán)境下頻繁使用,性能要求較高,那么顯然使用餓漢模式來(lái)避免資源競(jìng)爭(zhēng),提高響應(yīng)速度更好。
懶漢模式
如果單例對(duì)象構(gòu)造十分耗時(shí)或者占用很多資源,比如加載插件啊, 初始化網(wǎng)絡(luò)連接啊,讀取文件啊等等,而有可能該對(duì)象程序運(yùn)行時(shí)不會(huì)用到,那么也要在程序一開始就進(jìn)行初始化,就會(huì)導(dǎo)致程序啟動(dòng)時(shí)非常的緩慢。 所以這種情況使用懶漢模式(延遲加載)更好。
懶漢模式的條件:
- 對(duì)象在main函數(shù)之后才會(huì)創(chuàng)建,不會(huì)影響啟動(dòng)順序
- 可以主動(dòng)控制創(chuàng)建順序
設(shè)計(jì)懶漢模式的步驟:(與餓漢模式基本相同)
- 將構(gòu)造函數(shù)設(shè)成private,以及封死拷貝構(gòu)造和重載賦值
- 定義成員變量,變量類型為
static 類型名
- 在類外初始化這個(gè)單例的對(duì)象
- 添加其它成員方法
與餓漢模式的區(qū)別:
- 對(duì)象在main函數(shù)之后才會(huì)創(chuàng)建,不會(huì)影響啟動(dòng)順序
- 可以主動(dòng)控制創(chuàng)建順序
- 將對(duì)象的創(chuàng)建改為在堆上創(chuàng)建
- 懶漢模式存在多個(gè)對(duì)象一起調(diào)用GetInstance的情況,存在線程安全的風(fēng)險(xiǎn),可能new出來(lái)多個(gè)對(duì)象,因此需要加鎖,需要新增一個(gè)鎖的成員對(duì)象,并定義為static類型;餓漢模式一開始就一個(gè)對(duì)象,不用創(chuàng)建,所以不用鎖。
注意:鎖不能被拷貝,因此定義鎖的成員變量時(shí)可以用指針(地址)或者引用的方式定義,C++采用地址的行為不常見,用引用更好。
加鎖也是有講究的,如果像這樣的代碼:
//多個(gè)對(duì)象一起調(diào)用GetInstance,存在線程安全的風(fēng)險(xiǎn),可能new出來(lái)多個(gè)對(duì)象,因此需要加鎖
static InfoSingleton& GetInstance()
{
_pmtx.lock();
if (_psins == nullptr)//避免對(duì)象new出來(lái)以后每次都加鎖,提高性能
{
_psins = new InfoSingleton;
}
_pmtx.unlock();
return *_psins;
}
由于這種方式每次都需要加鎖,但實(shí)際上只有第一次創(chuàng)建對(duì)象才需要加鎖,所以為了避免鎖影響效率,使用雙層if檢查;此外,對(duì)于new,一旦拋異常,就需要捕獲,此時(shí)可以使用try-catch,但這種寫法不可行,通過(guò)之前智能指針的RAII思想,我們可以自己設(shè)定一個(gè)類:基于RAII思想的管理類,來(lái)防止鎖的問(wèn)題。
為什么try-catch不可行,因?yàn)檫€在加鎖階段,一旦進(jìn)行捕獲跳轉(zhuǎn),那么這把鎖會(huì)一直鎖住,為了避免出現(xiàn)這種情況,才使用RAII的思想。C++線程庫(kù)中也有對(duì)應(yīng)的庫(kù)函數(shù)方法,但是這里仍然可以手撕一個(gè)。
//RAII的鎖管理類
template<class Lock>
class LockGuard
{
public:
LockGuard(Lock& lk)
:_lk(lk)
{
lk.lock();
}
~LockGuard()
{
_lk.unlock();
}
private:
Lock& _lk;//成員變量用引用-->避免拷貝
};
//懶漢模式
//1、對(duì)象在main函數(shù)之后才會(huì)創(chuàng)建,不會(huì)影響啟動(dòng)順序
//2、可以主動(dòng)控制創(chuàng)建順序
//問(wèn)題:
class InfoSingleton
{
public:
//多個(gè)對(duì)象一起調(diào)用GetInstance,存在線程安全的風(fēng)險(xiǎn),可能new出來(lái)多個(gè)對(duì)象,因此需要加鎖
static InfoSingleton& GetInstance()
{
//第一次獲取單例對(duì)象的時(shí)候創(chuàng)建對(duì)象
//雙檢查加鎖
if (_psins == nullptr)//避免對(duì)象new出來(lái)以后每次都加鎖,提高性能
{
// t1 t2
LockGuard<mutex> mtx(_smtx);
if (_psins == nullptr) //保證線程安全且只new一次
{
_psins = new InfoSingleton;
}
}
return *_psins;
}
void Insert(string name, int salary)
{
_info[name] = salary;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ":" << kv.second << endl;
}
}
private:
InfoSingleton()
{}
InfoSingleton(const InfoSingleton&) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
map<string, int> _info;
// ...
private:
static InfoSingleton* _psins;
static mutex _smtx;
};
InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;
int main()
{
InfoSingleton::GetInstance().Insert("張三", 10000);
InfoSingleton& infosl = InfoSingleton::GetInstance();
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
cout << endl;
InfoSingleton::GetInstance().Insert("張三", 18000);
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
return 0;
}
庫(kù)中也有對(duì)應(yīng)的加鎖方法:
static InfoSingleton& GetInstance()
{
//第一次獲取單例對(duì)象的時(shí)候創(chuàng)建對(duì)象
//雙檢查加鎖
if (_psins == nullptr)//避免對(duì)象new出來(lái)以后每次都加鎖,提高性能
{
// t1 t2
//LockGuard<mutex> mtx(_smtx);
std::lock_guard<mutex> lock(_smtx);//庫(kù)中的方法
if (_psins == nullptr) //保證線程安全且只new一次
{
_psins = new InfoSingleton;
}
}
return *_psins;
}
new出來(lái)之后是否需要釋放?
一般單例模式對(duì)象不需要考慮釋放。單例模式的類的一個(gè)對(duì)象通常在整個(gè)程序運(yùn)行期間都會(huì)使用,因此最后不delete也不會(huì)有問(wèn)題,只要進(jìn)程最終正常結(jié)束,對(duì)象的資源就會(huì)由OS自動(dòng)釋放。
什么時(shí)候單例模式的對(duì)象需要釋放?
單例對(duì)象不用時(shí),必須手動(dòng)處理,一些資源需要保存。假設(shè)工資名單需要保存到文件里,要求系統(tǒng)結(jié)束之前將信息保存進(jìn)去,此時(shí)就需要手動(dòng)處理。所以,可以新增一個(gè)方法DelInstance(),是否需要調(diào)用取決于自己:
//懶漢模式
//1、對(duì)象在main函數(shù)之后才會(huì)創(chuàng)建,不會(huì)影響啟動(dòng)順序
//2、可以主動(dòng)控制創(chuàng)建順序
class InfoSingleton
{
public:
//多個(gè)對(duì)象一起調(diào)用GetInstance,存在線程安全的風(fēng)險(xiǎn),可能new出來(lái)多個(gè)對(duì)象,因此需要加鎖
static InfoSingleton& GetInstance()
{
//第一次獲取單例對(duì)象的時(shí)候創(chuàng)建對(duì)象
//雙檢查加鎖
if (_psins == nullptr)//避免對(duì)象new出來(lái)以后每次都加鎖,提高性能
{
// t1 t2
//LockGuard<mutex> mtx(_smtx);
std::lock_guard<mutex> lock(_smtx);
if (_psins == nullptr) //保證線程安全且只new一次
{
_psins = new InfoSingleton;
}
}
return *_psins;
}
//一般單例對(duì)象不需要考慮釋放
//單例對(duì)象不用時(shí),必須手動(dòng)處理,一些資源需要保存
static void DelInstance()
{
//保存數(shù)據(jù)到文件
// ...
std::lock_guard<mutex> lock(_smtx);
if (_psins)
{
delete _psins;
_psins = nullptr;
}
}
void Insert(string name, int salary)
{
_info[name] = salary;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ":" << kv.second << endl;
}
}
private:
InfoSingleton()
{}
InfoSingleton(const InfoSingleton&) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
map<string, int> _info;
// ...
private:
static InfoSingleton* _psins;
static mutex _smtx;
};
InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;
int main()
{
InfoSingleton::GetInstance().Insert("張三", 10000);
InfoSingleton& infosl = InfoSingleton::GetInstance();
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
cout << endl;
InfoSingleton::GetInstance().Insert("張三", 18000);
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
InfoSingleton::DelInstance();//主動(dòng)調(diào)用
return 0;
}
如果忘記主動(dòng)調(diào)用,同樣會(huì)產(chǎn)生錯(cuò)誤,因此仍需要設(shè)計(jì)一個(gè)能夠自動(dòng)回收的方式,這里采用新增一個(gè)內(nèi)部類GC,利用RAII的思想,一旦忘主動(dòng)回收,其在main函數(shù)結(jié)束時(shí)就會(huì)自動(dòng)回收,此時(shí)就需要新增一個(gè)成員變量以及內(nèi)部類:
注:內(nèi)部類是外部類的友元
//懶漢模式
//1、對(duì)象在main函數(shù)之后才會(huì)創(chuàng)建,不會(huì)影響啟動(dòng)順序
//2、可以主動(dòng)控制創(chuàng)建順序
class InfoSingleton
{
public:
//多個(gè)對(duì)象一起調(diào)用GetInstance,存在線程安全的風(fēng)險(xiǎn),可能new出來(lái)多個(gè)對(duì)象,因此需要加鎖
static InfoSingleton& GetInstance()
{
//第一次獲取單例對(duì)象的時(shí)候創(chuàng)建對(duì)象
//雙檢查加鎖
if (_psins == nullptr)//避免對(duì)象new出來(lái)以后每次都加鎖,提高性能
{
// t1 t2
//LockGuard<mutex> mtx(_smtx);
std::lock_guard<mutex> lock(_smtx);
if (_psins == nullptr) //保證線程安全且只new一次
{
_psins = new InfoSingleton;
}
}
return *_psins;
}
//一般單例對(duì)象不需要考慮釋放
//單例對(duì)象不用時(shí),必須手動(dòng)處理,一些資源需要保存
static void DelInstance()
{
//保存數(shù)據(jù)到文件
// ...
std::lock_guard<mutex> lock(_smtx);
if (_psins)
{
delete _psins;
_psins = nullptr;
}
}
//忘記調(diào)用DelInstance(),自動(dòng)回收
class GC
{
public:
~GC()
{
if (_psins)
{
cout << " ~GC()" << endl;
DelInstance();
}
}
};
void Insert(string name, int salary)
{
_info[name] = salary;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ":" << kv.second << endl;
}
}
private:
InfoSingleton()
{}
InfoSingleton(const InfoSingleton&) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
map<string, int> _info;
// ...
private:
static InfoSingleton* _psins;
static mutex _smtx;
static GC _gc;
};
InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;
InfoSingleton::GC InfoSingleton::_gc;
int main()
{
InfoSingleton::GetInstance().Insert("張三", 10000);
InfoSingleton& infosl = InfoSingleton::GetInstance();
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
cout << endl;
InfoSingleton::GetInstance().Insert("張三", 18000);
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
return 0;
}
因此,可以主動(dòng)回收,也可以在程序結(jié)束時(shí)自動(dòng)回收,但單例對(duì)象一般不需要回收。
實(shí)現(xiàn)懶漢的另一種方式:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-538798.html
懶漢模式的實(shí)現(xiàn)還有另一種方式,直接static,也是只創(chuàng)建一次對(duì)象,所以下面的方式也可以,但不是一種通用的方式。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-538798.html
//是懶漢:因?yàn)殪o態(tài)的局部變量是在main函數(shù)之后才創(chuàng)建初始化的:局部靜態(tài)變量的初始化只初始化一次。
//C++11之前,不能保證sinst的初始化是線程安全的。
//C++11之后,可以。
class InfoSingleton
{
public:
//多個(gè)對(duì)象一起調(diào)用GetInstance,存在線程安全的風(fēng)險(xiǎn),可能new出來(lái)多個(gè)對(duì)象,因此需要加鎖
static InfoSingleton& GetInstance()
{
static InfoSingleton sinst;
return sinst;
}
void Insert(string name, int salary)
{
_info[name] = salary;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ":" << kv.second << endl;
}
}
private:
InfoSingleton()
{
cout << "InfoSingleton()" << endl;
}
InfoSingleton(const InfoSingleton&) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
map<string, int> _info;
// ...
private:
};
int main()
{
InfoSingleton::GetInstance().Insert("張三", 10000);
InfoSingleton& infosl = InfoSingleton::GetInstance();
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
cout << endl;
InfoSingleton::GetInstance().Insert("張三", 18000);
infosl.Insert("李四", 12000);
infosl.Insert("王五", 15000);
infosl.Insert("趙六", 11000);
infosl.Print();
return 0;
}
到了這里,關(guān)于【C++修煉之路】33.特殊類設(shè)計(jì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!