zoukankan      html  css  js  c++  java
  • 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加价一倍】

  • 相关阅读:
    React生命周期, 兄弟组件之间通信
    React组件式编程Demo-用户的增删改查
    React之this.refs, 实现数据双向绑定
    CCF CSP 201812-4 数据中心
    CCF CSP 201812-4 数据中心
    PAT 顶级 1020 Delete At Most Two Characters (35 分)
    PAT 顶级 1020 Delete At Most Two Characters (35 分)
    Codeforces 1245C Constanze's Machine
    Codeforces 1245C Constanze's Machine
    CCF CSP 201712-4 行车路线
  • 原文地址:https://www.cnblogs.com/fullsail/p/2842618.html
Copyright © 2011-2022 走看看