首先要說明,這個准確說並不是BOOST的singleton實現,而是BOOST的POOL庫的singleton實現。BOOST庫中其實有若干個singleton模版,這個只是其中一個。但網上大部分介紹的介紹的BOOST的Singleton實現都是這個,所以大家也就默認了。而且這個的確算是比較特殊和有趣的一個實現。
網上比較有名的文章是這篇《2B程序員,普通程序員和文藝程序員的Singleton實現》 介紹,我雖然對Singleton模版無愛,但自己的項目組中也有人用這個實現,所以還是研究了一下這個實現,特別網上真正解釋清楚這個東東的人並不多(包括原文),所以還是研究了一下。
1 為啥2B實現有問題
為了介紹清楚這個實現,我們還要先解釋清楚為啥2B實現有問題,首先說明,2B實現和BOOST的實現都可以解決多線程調用Singleton導致多次初始化的問題。
1 //H文件 2 template <typename T> class Singleton_2B 3 { 4 protected: 5 typedef T object_type; 6 //利用的是類的靜態全局變量 7 static T instance_; 8 public: 9 static T* instance() 10 { 11 return &instance_; 12 } 13 }; 14 15 //因為是類的靜態變量,必須有一個通用的聲明 16 template<typename T> typename Singleton_2B<T>::object_type Singleton_2B<T>::instance_; 17 18 //測試的例子代碼。 19 class Object_2B_1 20 { 21 22 //其實使用友元幫助我們可以讓Object_2B的構造函數是protected的,從而真正實現單子的意圖 23 friend class Singleton_2B<Object_2B_1>; 24 //注意下面用protected,大家無法構造實例 25 protected: 26 Object_2B_1(); 27 ~Object_2B_1(){}; 28 public: 29 void do_something(); 30 protected: 31 int data_2b_1_; 32 }; 33 34 class Object_2B_2 35 { 36 friend class Singleton_2B<Object_2B_2>; 37 protected: 38 Object_2B_2(); 39 ~Object_2B_2(){}; 40 public: 41 void do_something(); 42 protected: 43 int data_2b_2_; 44 }; 45 46 //CPP文件 47 Object_2B_1::Object_2B_1(): 48 data_2b_1_(1) 49 { 50 printf("Object_2B_1::Object_2B_1() this:[%p] data_2b_1_ [%d].\n",this,data_2b_1_); 51 Singleton_2B<Object_2B_2>::instance()->do_something(); 52 }; 53 54 void Object_2B_1::do_something() 55 { 56 data_2b_1_+= 10000; 57 printf("Object_2B_1::do_something() this:[%p] data_2b_1_ [%d].\n",this,data_2b_1_); 58 59 } 60 61 62 Object_2B_2::Object_2B_2(): 63 data_2b_2_(2) 64 { 65 printf("Object_2B_2::Object_2B_2() this:[%p] data_2b_2_ [%d].\n",this,data_2b_2_); 66 Singleton_2B<Object_2B_1>::instance()->do_something(); 67 }; 68 69 void Object_2B_2::do_something() 70 { 71 data_2b_2_+= 10000; 72 printf("Object_2B_2::do_something() this:[%p] data_2b_2_ [%d].\n",this,data_2b_2_); 73 }
但會導致什么問題呢?崩潰?不一定是,(因為靜態數據區的空間應該是先分配的),而且結果這個和編譯器的實現有關系,
GCC的輸出結果如下:
1 //GCC編譯器的輸出如下: 2 Object_2B_2::Object_2B_2() this:[0046E1F4] data_2b_2_ [2]. 3 //注意下面,do_something函數被調用了,但是沒有崩潰,data_2b_1_默認被初始化為了0 4 Object_2B_1::do_something() this:[0046E1F0] data_2b_1_ [10000]. 5 //注意下面,do_something函數被調用了后,構造函數才起作用,data_2b_1_又被初始化為了1, 6 Object_2B_1::Object_2B_1() this:[0046E1F0] data_2b_1_ [1]. Object_2B_2::do_something() this:[0046E1F4] data_2b_2_ [10002].
VS2010的DEBUG版本的輸出和GCC一致,但有意思的是Realse版本輸出結果如下:
1 //VC++2010的release版本輸出 2 Object_2B_2::Object_2B_2() this:[0046E1F4] data_2b_2_ [2]. 3 //注意下面的10001,感覺就像構造函數被偷偷調用過一樣。有意思。 4 Object_2B_1::do_something() this:[0046E1F0] data_2b_1_ [10001]. 5 Object_2B_1::Object_2B_1() this:[0046E1F0] data_2b_1_ [1]. Object_2B_2::do_something() this:[0046E1F4] data_2b_2_ [10002].
2 BOOST的實現如何規避問題
接着我們就來看看BOOST的模版是使用什么技巧,即保證多線程下不重復初始化,又讓相互之間的調用更加安全。
1 template <typename T> 2 class Singleton_WY 3 { 4 private: 5 struct object_creator 6 { 7 object_creator() 8 { 9 Singleton_WY<T>::instance(); 10 } 11 inline void do_nothing() const {} 12 }; 13 //利用類的靜態對象object_creator的構造初始化,在進入main之前已經調用了instance 14 //從而避免了多次初始化的問題 15 static object_creator create_object_; 16 public: 17 static T *instance() 18 { 19 static T obj; 20 //do_nothing 是必要的,do_nothing的作用有點意思, 21 //如果不加create_object_.do_nothing();這句話,在main函數前面 22 //create_object_的構造函數都不會被調用,instance當然也不會被調用, 23 //我的估計是模版的延遲實現的特效導致,如果沒有這句話,編譯器也不會實現 24 // Singleton_WY<T>::object_creator,所以就會導致這個問題 25 create_object_.do_nothing(); 26 return &obj; 27 } 28 }; 29 //因為create_object_是類的靜態變量,必須有一個通用的聲明 30 template <typename T> typename Singleton_WY<T>::object_creator Singleton_WY<T>::create_object_; 31 32 //測試的例子 33 class Object_WY_1 34 { 35 //其實使用友元幫助我們可以讓Object_2B的構造函數是protected的,從而真正實現單子的意圖 36 friend class Singleton_WY<Object_WY_1>; 37 //注意下面用protected,大家無法構造實例 38 protected: 39 Object_WY_1(); 40 ~Object_WY_1(){}; 41 public: 42 void do_something(); 43 protected: 44 int data_wy_1_; 45 }; 46 47 class Object_WY_2 48 { 49 friend class Singleton_WY<Object_WY_2>; 50 protected: 51 Object_WY_2(); 52 ~Object_WY_2(){}; 53 public: 54 void do_something(); 55 protected: 56 int data_wy_2_; 57 }; 58 59 //CPP代碼 60 Object_WY_1::Object_WY_1(): 61 data_wy_1_(1) 62 { 63 printf("Object_WY_1::Object_WY_1() this:[%p] data_2b_1_ [%d].\n",this,data_wy_1_); 64 Singleton_WY<Object_WY_2>::instance()->do_something(); 65 }; 66 67 void Object_WY_1::do_something() 68 { 69 data_wy_1_+= 10000; 70 printf("Object_2B_1::do_something() this:[%p] data_2b_1_ [%d].\n",this,data_wy_1_); 71 72 } 73 74 75 Object_WY_2::Object_WY_2(): 76 data_wy_2_(2) 77 { 78 printf("Object_WY_2::Object_WY_2() this:[%p] data_2b_2_ [%d].\n",this,data_wy_2_); 79 Singleton_WY<Object_WY_1>::instance()->do_something(); 80 }; 81 82 void Object_WY_2::do_something() 83 { 84 data_wy_2_+= 10000; 85 printf("Object_WY_2::do_something() this:[%p] data_2b_2_ [%d].\n",this,data_wy_2_); 86 }
1 Object_WY_2::Object_WY_2() this:[00ECA138] data_2b_2_ [2]. 2 Object_WY_1::Object_WY_1() this:[00ECA140] data_2b_1_ [1]. 3 Object_WY_2::do_something() this:[00ECA138] data_2b_2_ [10002]. 4 Object_2B_1::do_something() this:[00ECA140] data_2b_1_ [10001].
首先BOOST的這個實現的Singleton的數據分成兩個部分,一個是內部類的object_creator的靜態成員creator_object_,一個是instance函數內部的靜態變量static T obj;如果外部的有人調用了instance()函數,靜態變量obj就會被構造出來,而靜態成員creator_object_會在main函數前面構造,他的構造函數內部也調用instance(),這樣就會保證靜態變量一定會在main函數前面初始化出來。
到此為止,這部分還都能正常理解,但instance()函數中的這句就是有點詭異技巧的了。
create_object_.do_nothing();
其實這句話如果單獨分析,並沒有明確的作用,因為如果類的靜態成員creator_object_的構造就應該讓單子對象被初始化。但一旦你注釋掉這句話,你會發現create_object_的構造函數都不會被調用。在main函數之前,什么事情都沒有發生(VC++2010和GCC都一樣),BOOST的代碼注釋只說是確保create_object_的構造被調用,但也沒有明確原因。
我估計這還是和模版的編譯有潛在的關系,模版都是Lazy Evaluation。所以如果編譯器沒有編譯過create_object_.do_nothing();編譯器就會漏掉create_object_的對象一切實現,也就完全不會編譯Singleton_WY<T>::object_creator和Singleton_WY<T>:: create_object_代碼,所以就會導致這個問題。使用dumpbin 分析去掉前后的obj文件,大約可以證明這點。所以create_object_.do_nothing();這行代碼必須要有。
3 個人感覺
也許是因為我本身對Singleton的模版就不感冒,我對文藝青年的這個Singleton也沒有太大胃口,
一方面是技巧性過強,我不才,do_nothing()那句話的問題我研究了半天。
二是由於他將所有的instance初始化放在了main函數前面,好處是避免了多線程多次初始化的麻煩,但也限制了初始化的多樣性。一些太強的邏輯關系的情況下這招並不好。
三是這種依靠類static變量的方式,無法按需啟動,回收。
四是性能,每次do_nothine也是無謂的消耗呀。
為了一個很簡單的風險(多次初始化),引入一個技巧性很強的又有各種限制的東東。是否有點畫蛇添足。YY。
告別2012,迎接2013,
【本文作者是雁渡寒潭,本着自由的精神,你可以在無盈利的情況完整轉載此文檔,轉載時請附上BLOG鏈接:http://www.cnblogs.com/fullsail/ 或者http://blog.csdn.net/fullsail,否則每字一元,每圖一百不講價。對Baidu文庫,360doc加價一倍】