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 }
但会导致什么问题呢?崩溃?不一定是,(因为静态数据区的空间应该是先分配的),而且结果这个和编译器的实现有关系,
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加价一倍】