目錄前言無法調用析構函數的原因改進方法內嵌回收類智能指針局部靜態變數參考文章 前言 在《單例模式學習》中提到了,在單例對象是通過new關鍵字動態分配在堆上的情況下,當程式退出時,不會通過C++的RAII機制自動調用其析構函數。本文討論一下這種現象的原因以及解決方法。 無法調用析構函數的原因 在DCL ...
目錄
前言
在《單例模式學習》中提到了,在單例對象是通過new
關鍵字動態分配在堆上的情況下,當程式退出時,不會通過C++的RAII機制自動調用其析構函數。本文討論一下這種現象的原因以及解決方法。
無法調用析構函數的原因
在DCLP(雙檢查鎖模式)中,CSingleton中的instance
是一個靜態指針變數,被分配在全局/靜態存儲區。而instance
所指向的CSingleton實例是通過new
創建在堆上的,只能手動調用delete來釋放相關資源(對於單例模式這是無法實現的,因為析構函數私有),無法通過RAII釋放相關資源。
在程式結束時,instance
這個指針變數被銷毀了,但它所指向的記憶體空間中的CSingleton對象並沒有被顯式銷毀,而是由操作系統去回收這一塊記憶體(不會調用其析構函數)。然而依賴操作系統來清理資源並不是一個優雅的結束方式,可能會造成文件句柄未關閉、網路連接未斷開等資源泄漏。
class CSingleton
{
public:
static CSingleton* getInstance();
static std::mutex mtx;
private:
CSingleton(){}
~CSingleton(){}
CSingleton(const CSingleton&) = delete;
CSingleton& operator=(const CSingleton&) = delete;
static CSingleton* instance;
};
CSingleton* CSingleton::instance;
CSingleton* CSingleton::getInstance()
{
if(nullptr == instance)
{
mtx.lock();
if(nullptr == instance)
{
instance = new CSingleton();
}
mtx.unlock();
}
return instance;
}
改進方法
在討論改進方法時,我們還是傾向於利用C++的RAII機制,而不是手動去控制釋放的時機。
內嵌回收類
我們的單例類對象生命周期的開始是在第一次調用時,結束是在程式結束時。
而且我們知道①靜態成員變數的生命周期是從程式啟動到結束②在靜態成員變數被銷毀時會調用其析構函數
因此我們可以在單例類中定義一個用於釋放單例類資源的內嵌類,將其析構函數定義為顯式刪除單例對象的操作,然後在單例類中添加一個內嵌類類型的靜態成員變數garbo
。
這樣的話,在程式結束時garbo
就會被銷毀,而RAII機制確保了在銷毀時會調用內嵌類CGarbo
的析構函數。
因為在~CGarbo()
中delete了CSingleton::instance
,所以~CSingleton()
就會被調用,相關資源得以釋放。
class CSingleton
{
public:
static CSingleton* getInstance();
private:
CSingleton(){std::cout<<"創建了一個對象"<< std::endl;}
~CSingleton(){std::cout<<"銷毀了一個對象"<< std::endl;}
CSingleton(const CSingleton&) = delete;
CSingleton& operator=(const CSingleton&) = delete;
static CSingleton* instance;
static std::mutex mtx;
class CGarbo
{
public:
CGarbo(){}
~CGarbo()
{
if(nullptr != CSingleton::instance) //嵌套類可訪問外層類的私有成員
{
delete CSingleton::instance;
instance = nullptr;
}
std::cout<<"Garbo worked"<< std::endl;
}
};
static CGarbo garbo; //定義一個靜態成員,程式結束時,系統會自動調用它的析構函數
};
CSingleton* CSingleton::instance;
std::mutex CSingleton::mtx;
CSingleton* CSingleton::getInstance()
{
...
}
CSingleton::CGarbo CSingleton::garbo; //還需要初始化一個垃圾清理的靜態成員變數
運行結果:
智能指針
我們還可以利用智能指針引用計數機制,對資源自動管理:
//編譯不通過
class CSingleton
{
public:
static std::shared_ptr<CSingleton> getInstance();
private:
CSingleton(){std::cout<<"創建了一個對象"<<std::endl;}
~CSingleton(){std::cout<<"銷毀了一個對象"<<std::endl;}
CSingleton(const CSingleton&) = delete;
CSingleton& operator=(const CSingleton&) = delete;
static std::shared_ptr<CSingleton> instance;
static std::mutex mutex;
};
std::shared_ptr<CSingleton> CSingleton::instance;
std::mutex CSingleton::mutex;
std::shared_ptr<CSingleton> CSingleton::getInstance()
{
if (nullptr == instance)
{
std::lock_guard<std::mutex> lock(mutex);
if (nullptr == instance)
{
instance = std::shared_ptr<CSingleton>(new CSingleton());
}
}
return instance;
}
註意上述代碼無法通過編譯,原因是當std::shared_ptr
被銷毀時,它會嘗試使用delete來銷毀管理的對象。但因為CSingleton的析構函數是私有的,所以無法從外部手動銷毀CSingleton實例。
要解決這個問題,我們需要在CSingleton中自定義一個刪除器,讓std::shared_ptr
能夠調用私有析構函數。
class CSingleton
{
public:
static std::shared_ptr<CSingleton> getInstance();
private:
CSingleton(){std::cout<<"創建了一個對象"<<std::endl;}
~CSingleton(){std::cout<<"銷毀了一個對象"<<std::endl;}
CSingleton(const CSingleton&) = delete;
CSingleton& operator=(const CSingleton&) = delete;
static std::shared_ptr<CSingleton> instance;
static std::mutex mutex;
static void deleter(CSingleton* p); //自定義刪除器
};
std::shared_ptr<CSingleton> CSingleton::instance;
std::mutex CSingleton::mutex;
std::shared_ptr<CSingleton> CSingleton::getInstance()
{
if (nullptr == instance)
{
std::lock_guard<std::mutex> lock(mutex);
if (nullptr == instance)
{
instance = std::shared_ptr<CSingleton>(new CSingleton(),CSingleton::deleter);
}
}
return instance;
}
void CSingleton::deleter(CSingleton* p)
{
delete p;
std::cout<<"deleter worked"<<std::endl;
}
測試結果:
局部靜態變數
局部靜態變數形式的單例模式也可以完成資源的釋放,詳見《單例模式學習》。
static CSingleton& getInstance()
{
static CSingleton instance;
return instance;
}