BOOST的Singleton模版詳解


首先要說明,這個准確說並不是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 }
如代碼:Singleton_2B是一個singleton的模板封裝,根據這個模版,我們實現了2個單子類,Object_2B_1和Object_2B_2,為了模仿問題,我們在其各自的構造函數里面又都調用了其他一個對象的instance函數。因為我們知道,全局和類static 變量的初始化是編譯器自己控制的,我們無法干涉,所以如果假設Object_2B_1::instance_靜態成員變量被先構造,他的構造函數調用里面調用Object_2B_2::instance().do_something()函數時,Object_2B_2::instance_可能還沒有構造出來。從而導致問題。

但會導致什么問題呢?崩潰?不一定是,(因為靜態數據區的空間應該是先分配的),而且結果這個和編譯器的實現有關系,

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 }
調用輸出的結果如下,大家可以發現調用順序正確了Object_WY_2的構造函數內部調用:Singleton_WY<Object_WY_1>::instance()函數的時候,Object_WY_1的單子實例就被創建出來了。
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加價一倍】

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM