題目:設計一個類,我們只能生成該類的一個實體,
解法一:單執行緒解法
//缺點:多執行緒情況下,每個執行緒可能創建出不同的的Singleton實體
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton* getInstance()
{
if(m_pInstance == nullptr)
{
m_pInstance = new Singleton();
}
return m_pInstance;
}
static void destroyInstance()
{
if(m_pInstance != nullptr)
{
delete m_pInstance;
m_pInstance = nullptr;
}
}
private:
Singleton(){}
static Singleton* m_pInstance;
};
Singleton* Singleton::m_pInstance = nullptr;
// 單執行緒獲取多次實體
void Test1(){
// 預期結果:兩個實體指標指向的地址相同
Singleton* singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
Singleton* singletonObj2 = Singleton::getInstance();
cout << singletonObj2 << endl;
Singleton::destroyInstance();
}
int main(){
Test1();
return 0;
}
解法二:多執行緒+加鎖
/*解法一是最簡單,也是最普遍的實作方式,但是,這種實作方式有很多問題,比如沒有考慮多執行緒的問題,在多執行緒的情況下,就可能會創建多個Singleton實體,以下是改善的版本,*/
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
static mutex m_mutex; // 互斥量
Singleton(){}
static Singleton* m_pInstance;
public:
static Singleton* getInstance(){
if(m_pInstance == nullptr){
m_mutex.lock(); // 使用C++11中的多執行緒庫
if(m_pInstance == nullptr){ // 兩次判斷是否為NULL的雙重檢查
m_pInstance = new Singleton();
}
m_mutex.unlock();
}
return m_pInstance;
}
static void destroyInstance(){
if(m_pInstance != nullptr){
delete m_pInstance;
m_pInstance = nullptr;
}
}
};
Singleton* Singleton::m_pInstance = nullptr;
mutex Singleton::m_mutex;
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多個行程獲得單例
void Test1(){
// 預期結果,列印出相同的地址,之間可能缺失換行符,也屬正常現象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
int main(){
Test1();
Singleton::destroyInstance();
return 0;
}
/*此方法中進行了兩次m_pInstance == nullptr的判斷,使用了所謂的“雙檢鎖”機制,因為進行一次加鎖和解鎖是需要付出對應的代價的,而進行兩次判斷,就可以避免多次加鎖與解鎖操作,只在m_pInstance不為nullptr時才需要加鎖,同時也保證了執行緒安全,但是,如果進行大資料的操作,加鎖操作將成為一個性能的瓶頸,為此,一種新的單例模式的實作也就出現了,*/
解法三:const static型實體
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
static const Singleton* m_pInstance;
public:
static Singleton* getInstance(){
return const_cast<Singleton*>(m_pInstance); // 去掉“const”特性
// 注意!若該函式的回傳值改為const static型,則此處不必進行const_cast靜態轉換
// 所以該函式可以改為:
/*
const static Singleton* getInstance(){
return m_pInstance;
}
*/
}
static void destroyInstance(){
if(m_pInstance != NULL){
delete m_pInstance;
m_pInstance = NULL;
}
}
};
const Singleton* Singleton::m_pInstance = new Singleton(); // 利用const只能定義一次,不能再次修改的特性,static繼續保持類內只有一個實體
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多個行程獲得單例
void Test1(){
// 預期結果,列印出相同的地址,之間可能缺失換行符,也屬正常現象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
int main(){
Test1();
Singleton::destroyInstance();
return 0;
}
/*因為靜態初始化在程式開始時,也就是進入主函式之前,由主執行緒以單執行緒方式完成了初始化,所以靜態初始化實體保證了執行緒安全性,在性能要求比較高時,就可以使用這種方式,從而避免頻繁的加鎖和解鎖造成的資源浪費,由于上述三種實作,都要考慮到實體的銷毀,關于實體的銷毀,待會在分析,*
解法四:在get函式中創建并回傳static臨時實體的參考
//PS:該方法不能認為控制單例實體的銷毀
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
public:
static Singleton* getInstance(){
static Singleton m_pInstance; // 注意,宣告在該函式內
return &m_pInstance;
}
};
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多個行程獲得單例
void Test1(){
// 預期結果,列印出相同的地址,之間可能缺失換行符,也屬正常現象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
// 單個行程獲得多次實體
void Test2(){
// 預期結果,列印出相同的地址,之間換行符分隔
print_singleton_instance();
print_singleton_instance();
}
int main(){
cout << "Test1 begins: " << endl;
Test1();
cout << "Test2 begins: " << endl;
Test2();
return 0;
}
解法五:最終方案,最簡&顯式控制實體銷毀
/*在實際專案中,特別是客戶端開發,其實是不在乎這個實體的銷毀的,因為,全域就這么一個變數,全域都要用,它的生命周期伴隨著軟體的生命周期,軟體結束了,他就自然而然結束了,因為一個程式關閉之后,它會釋放它占用的記憶體資源的,所以,也就沒有所謂的記憶體泄漏了,
但是,有以下情況,是必須要進行實體銷毀的:
在類中,有一些檔案鎖了,檔案句柄,資料庫連接等等,這些隨著程式的關閉而不會立即關閉的資源,必須要在程式關閉前,進行手動釋放,*/
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
static Singleton* m_pInstance;
// **重點在這**
class GC // 類似Java的垃圾回收器
{
public:
~GC(){
// 可以在這里釋放所有想要釋放的資源,比如資料庫連接,檔案句柄……等等,
if(m_pInstance != NULL){
cout << "GC: will delete resource !" << endl;
delete m_pInstance;
m_pInstance = NULL;
}
};
};
// 內部類的實體
static GC gc;
public:
static Singleton* getInstance(){
return m_pInstance;
}
};
Singleton* Singleton::m_pInstance = new Singleton();
Singleton::GC Singleton::gc;
void print_instance(){
Singleton* obj1 = Singleton::getInstance();
cout << obj1 << endl;
}
// 多執行緒獲取單例
void Test1(){
// 預期輸出:相同的地址,中間可能缺失換行符,屬于正常現象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_instance));
}
for(auto& thr : threads){
thr.join();
}
}
// 單執行緒獲取單例
void Test2(){
// 預期輸出:相同的地址,換行符分隔
print_instance();
print_instance();
print_instance();
print_instance();
print_instance();
}
int main()
{
cout << "Test1 begins: " << endl;
cout << "預期輸出:相同的地址,中間可以缺失換行(每次運行結果的排列格式通常不一樣)," << endl;
Test1();
cout << "Test2 begins: " << endl;
cout << "預期輸出:相同的地址,每行一個," << endl;
Test2();
return 0;
}
/*在程式運行結束時,系統會呼叫Singleton的靜態成員GC的解構式,該解構式會進行資源的釋放,而這種資源的釋放方式是在程式員“不知道”的情況下進行的,而程式員不用特別的去關心,使用單例模式的代碼時,不必關心資源的釋放,
那么這種實作方式的原理是什么呢?由于程式在結束的時候,系統會自動析構所有的全域變數,系統也會析構所有類的靜態成員變數,因為靜態變數和全域變數在記憶體中,都是存盤在靜態存盤區的,所有靜態存盤區的變數都會被釋放,由于此處是用了一個內部GC類,而該類的作用就是用來釋放資源,這種技巧在C++中是廣泛存在的,參見《C++中的RAII機制》,*/
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/37777.html
標籤:設計模式
上一篇:長久養成的打卡習慣可千萬不能丟呀
