几种singleton之实现解析

本文探讨了几种C++中实现Singleton模式的方法,包括常见做法、Boost库和ACE框架的实现。着重讨论了线程安全问题,分析了不同实现在线程环境下的表现,以及在多线程环境下如何确保Singleton的正确初始化。同时提出了几个关于Boost Singleton实现的疑问,期待读者解答。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在工作中使用单例的时机少过,以前自己实现,后来使用boost中的serialzation/singleton.hpp的单例。singleton是一个很小巧的东西,但是它的设计良好与否,倒是能反映设计者的知识与设计哲学。

 

曾经有看过一些文章讲述各种实现方式,似乎并不系统与详实。今天上午同事说在单例中不都有if(xx == null) {}这样的代码吗? 我告诉他NO。 那只是一种实现方式,或许我们常用,但并不代表就只能那样做。接下来我们就看看我们常写的几种单例实现,和boost中的两种单例实现,还有在ACE中单例的实现的不同吧。

 

singleton 实现一:

class singleton_test
{
    private:
        singleton_test(){}; //一般来说单例的构造函数都是private的
    public:
        static singleton_test* get_instance() //一般这个接口都是static的
        {
            if(!_instance)
            {
                _instance = new singleton_test();
            }
            return _instance;
        }
        void test()
        {
            cout<<"singleton_test: _instance is " << _instance <<endl;
        }

    private:
        static singleton_test*  _instance; //_instance的初始化需要在类外进行,如下面
};

//需要对_instance进行初始化
singleton_test::singleton_test* singleton_test::_instance = NULL;

int main()
{
    singleton_test::get_instance()->test();

    return 0;
}


 

评价:上面这种是我在工作中常用的把某个类单例化的做法 。但因为单例化本身是一个独立的东西,参杂在功能类中并不太合适,那我们写个模版类如何?

 

singleton实例二:

实例二(1)

template <typename  T>
class singleton
{
    private:
        singleton(){}

    public:
        static T& get_instance()
        {
            return _obj;
        }
    private:
        static T _obj;
};

template<typename T>
T singleton<T>::_obj;



另一种做法:

 

实例二(2)

template <typename  T>
class singleton_test
{
    private:
        singleton_test(){}
    public:
        static T& get_instance()
        {
            static T _obj;
            return _obj;
        }
};



这两种做法有什么区别呢?把static T _obj; 放的位置修改了一下而已。

effectie c++的条款4中提到:

(global对象,定义在namespace内的对象,class内的static对象,函数内的static对象,file作用域内的static对象)统称为static对象。

其中函数内的static对象又叫local static object, 其他的叫non-local static object。

non-local static object的初始化顺序是没有定义的,local static object在函数第一次调用时构造初始化。

还有:non-local static object会在main函数之前被初始化。

 

(从http://www.cnblogs.com/kex1n/archive/2011/04/05/2006194.html看到对此的描述,话说effective c++看完好久,细节的东西仍然是忘记太快了。)

  

但是!!看似完美的东西,一旦放在多线程环境下,可能就错漏百出了。实例二(2)这里的static T _obj; 在编译后大概形如 if(_obj is not init){ init obj}。可以试想在多线程环境下将会出现的几次init?

(这个我没有考证过,但是我想应该是这么回事)

于是乎,我们想怎么样才能设计出线程安全的singleton版本呢? 加锁? 这是我们最容易想到的一种办法。 好的,ACE就是这么做的,而且使用了所谓的“Double Checked Locking保证线程安全”。

 

singleton实例三:

 

template <class TYPE, class ACE_LOCK> TYPE *

ACE_Singleton<TYPE, ACE_LOCK>::instance (void)

{

   ACE_TRACE ("ACE_Singleton<TYPE, ACE_LOCK>::instance");

   ACE_Singleton<TYPE, ACE_LOCK> *&singleton =

     ACE_Singleton<TYPE, ACE_LOCK>::instance_i ();

   // Perform the Double-Check pattern...

   if (singleton == 0)

     {

           static ACE_LOCK *lock = 0;

           if (ACE_Object_Manager::get_singleton_lock (lock) != 0)

             // Failed to acquire the lock!

             return 0;

           ACE_GUARD_RETURN (ACE_LOCK, ace_mon, *lock, 0);

           if (singleton == 0)

             {

               ACE_NEW_RETURN (singleton, (ACE_Singleton<TYPE, ACE_LOCK>), 0);

               // Register for destruction with ACE_Object_Manager.

               ACE_Object_Manager::at_exit (singleton);

             }

     }

   return &singleton->instance_;

}


 

形式回归到我们开头在instance()时才new出实例的设计(顺便想想这种做法和实例二(1)的做法的区别),并且采用了两次check singleton == 0的做法。

为什么两次? 想知道原因我建议看一下这个博文:(http://www.blogjava.net/killvin/archive/2006/09/13/69499.html)。我认为这里讲得很透彻了,确实是很好的一个设计:)

 

那么boost里面怎么实现的呢,在之前的boost库提供了两个singleton类(其实是个附属品),我今天突然发现在我另一台电脑的1.51.0版本中已经在pool/detail/找不到singleton.hpp文件了,但另一个还躺在那里,它的位置是:boost/serialization/singleton.hpp。其实两个实现原理类似,估计这是其中一个被out掉的原因吧。

可是我仍然把pool/detail/singleton.hpp也贴出来。

 

singleton实例四:

 

// T must be: no-throw default constructible and no-throw destructible
template <typename T>
struct singleton_default
{
  private:
    struct object_creator
    {
      // This constructor does nothing more than ensure that instance()
      //  is called before main() begins, thus creating the static
      //  T object before multithreading race issues can come up.
      object_creator() { singleton_default<T>::instance(); }
      inline void do_nothing() const { }
    };
    static object_creator create_object;

    singleton_default();

  public:
    typedef T object_type;

    // If, at any point (in user code), singleton_default<T>::instance()
    //  is called, then the following function is instantiated.
    static object_type & instance()
    {
      // This is the object that we return a reference to.
      // It is guaranteed to be created before main() begins because of
      //  the next line.
      static object_type obj;

      // The following line does nothing else than force the instantiation
      //  of singleton_default<T>::create_object, whose constructor is
      //  called before main() begins.
      create_object.do_nothing();

      return obj;
    }
};
template <typename T>
typename singleton_default<T>::object_creator
singleton_default<T>::create_object;

 

它是线程安全的,但是有条件的(这个条件一般情况下我们的程序都满足:)。

究其原因大概是由于c++本身不保证non-local static object的初始化顺序,所以这个库的实现者在注释中写了如下话:

//The classes below support usage of singletons, including use in
//  program startup/shutdown code, AS LONG AS there is only one thread
//  running before main() begins, and only one thread running after main()
//  exits.

 

这里没有使用锁机制,而是充分利用了C++的语言特性较好的解决了多线程情况下使用singleton的问题。
boost的singleton的实现基于以下假设:良好的设计在进入main函数之前应该是单线程的。
我们可以使用全局变量的方式来设计singleton,并且保证在使用该singleton之前其已经被正确的初始化。

 

另外,这个做法,正好解决了我们实例二中遇到问题,那个问题是:在函数内的static初始化时机为运行时,导致我们的实例2的实现是非线程安全的。

这个boost的实现,我认为它引入的object_creator类,并且在它的构造函数中调用singleton::instance()。把原来的初始化时机从运行时,

提前到了如其它的non-local static object会在main函数之前被初始化。我想,这样算是相对的线程安全咯:)似乎比起ACE的实现来说稍逊一筹?

上面源码中的do_nothing()有什么用?有看过他人的说法,但这确实会让人困惑。 我希望得到你的合理解答。接着看boost最后留存的惟一一个singleton实现。

 

singleton实例五:

template<class T>
class singleton_wrapper : public T
{
public:
    static bool m_is_destroyed;
    ~singleton_wrapper(){
        m_is_destroyed = true;
    }
};

template<class T>
bool detail::singleton_wrapper<T>::m_is_destroyed = false;

} // detail

template <class T>
class singleton : public singleton_module
{
private:
    BOOST_DLLEXPORT static T & instance;
    // include this to provoke instantiation at pre-execution time
    static void use(T const &) {}
    BOOST_DLLEXPORT static T & get_instance() {
        static detail::singleton_wrapper<T> t;
        // refer to instance, causing it to be instantiated (and
        // initialized at startup on working compilers)
        assert(! detail::singleton_wrapper<T>::m_is_destroyed);
        use(instance);
        return static_cast<T &>(t);
    }
public:
    BOOST_DLLEXPORT static T & get_mutable_instance(){
        assert(! is_locked());
        return get_instance();
    }
    BOOST_DLLEXPORT static const T & get_const_instance(){
        return get_instance();
    }
    BOOST_DLLEXPORT static bool is_destroyed(){
        return detail::singleton_wrapper<T>::m_is_destroyed;
    }
};

template<class T>
BOOST_DLLEXPORT T & singleton<T>::instance = singleton<T>::get_instance();


 看boost两个版本的singleton的实现,我总结如下:

1)对于static T _t;的声明,倾向于将期放到get_instance()函数内,之所以放在函数内而非作为成员变量,前文有述其区别。

2)利用某些技术使放在函数内的原本会在运行期执行的,让它被提前到main()之前,以实现所谓线程安全。

 

遗留的问题:

1)为什么两个版本的都会类似do_nothing(),use()的东西,到底有何用?

2)第二个版本中,为什么需要使用singleton_wrapper包装一层呢? 后面的assert(! detail::singleton_wrapper<T>::m_is_destroyed); 用作何意?

3)第二个版本中,singleton_module类提供的lock()从来没用过。需要外部调用者自行lock? 感觉这里的lock并非处理线程安全,而是逻辑上的提供一种锁。其存在的意义是?

 

我的研究结果,我的困惑,求解。当然因为水平有限,可能文章会有各种错误,希望有心人能帮助,指导下偶:)

 

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值