在學習了C++中可以使用call_once
進行初始化資源后,我就想著寫一個單例模板供以后使用。
template<typename T>
class SingleTon {using Ptr = std::shared_ptr<T>;static Ptr p;static std::once_flag flag;template<typename ...Args>static void init(Args&&...args) {p.reset(new T(std::forward<Args>(args)...));}public:template<typename ...Args>static Ptr getInstance(Args&& ...args) {/*//也可以使用lambda表達式實現上面成員函數的功能,但是不是很必要,使用成員函數更加直觀而且避免了每次調用都創建lambda表達式和傳遞p的性能消耗auto init = [](Ptr &p, auto&& ...args1) {p.reset(new T(std::forward<decltype(args1)>(args1)...));};*///如果不使用lambda表達式而是使用靜態成員函數進行初始化,則必須在傳入給call_once的時候就進行實例化//否則編譯器會提示init是一個未解決的重載函數std::call_once(flag, init<Args...>, std::forward<Args>(args)...);//static Ptr p = std::make_shared<T>(std::forward<Args>(args)...);return p;}
};template<typename T>
std::shared_ptr<T> SingleTon<T>::p;template<typename T>
std::once_flag SingleTon<T>::flag;
但是非常不幸,如果我們簡單測試一下這個單例模式就會報錯。
#include "Singleton.h"
#include <iostream>
#include <string>int main() {auto p = SingleTon<std::string>::getInstance("Test");std::cout << *p << std::endl;return 0;
}
報錯信息:
terminate called after throwing an instance of 'std::system_error'what(): Unknown error -1
這不禁讓我非常疑惑,為什么看起來沒有什么問題的程序會報這么嚴重的錯誤呢?思考沒有頭緒后我在google上搜索了一下,發現已經有人遇到了這個問題:https://stackoverflow.com/questions/65335620/terminate-called-after-throwing-an-instance-of-stdsystem-error
大概的原因應該是因為使用的動態鏈接庫,我們沒有使用pthread_create
,所以就沒有pthread_create
的定義。然而call_once
又要使用pthread_create
(GNU C++ standard library std::call_once checks whether the application is multi-threaded by checking whether pthread_create can be resolved),所以就導致出現了這么嚴重的錯誤。
所以解決方案就是我們必須在多線程環境下使用call_once
。就我們這里來講,我們只需要讓程序里連接pthread
庫就可以了。(感謝@lingwq-lingwq的糾正,我剛開始以為是必須要創建一次線程)
自己對于靜態庫、動態庫的理解還是不夠深入,應該花時間再專門學習一下。
這里我們訪問模板類的靜態成員對象,雖然在每個文件中都生成了靜態成員對象,但是在鏈接的時候鏈接器將隨機選擇一個目標中的空間作為最終存儲空間,從而實現了多個文件中的實例化類模板共享同一套靜態成員。
C++11規定對于函數局部靜態變量的初始化只會在某一線程上單獨發生,在初始化完成之前,其他線程不會越過靜態數據的聲明而繼續運行。因此我們也可以用局部靜態變量來實現線程安全的單例模式。
template<typename T>
class SingleTon {using Ptr = std::shared_ptr<T>;public:static Ptr getInstance() {static Ptr p = std::make_shared<T>(); //對于靜態變量的初始化只會進行一次return p;}
};
這種實現簡單高效,而且沒有使用call_once
要求的多線程環境。因為是模板類,所以在實例化之前也不用擔心內存浪費,唯一的缺點就是不能夠在創建的時候傳入參數。
需要注意這里的getInstance
函數不能是模板函數,如果是模板函數那么傳入不同的參數就會得到不同的實例化,產生不同的局部靜態對象。