说来惭愧,大学没有好好学,第一次听到这个所谓Singleton的时候是在一个校园招聘会上。大致的题目就是要写一个Singleton,答案么大概也可以预计,就是什么都不知道, 呵呵。时过境迁,也写过了一些这样的东西,现在回过头来,还有很有意义的。不搞的特别另类,还是循着书中介绍的顺序来看看吧。
- class Singleton
- {
- public:
- static Singleton* Instance()
- {
- return &instance_;
- }
- int DoSomething();
- private:
- Singleton();
- Singleton(const Singleton&);
- Singleton& operator=(const Singleton&);
- ~Singleton();
- };
- Singleton Singleton::instance_;
大致看上去这个东西还是比较怪异的,怪异在什么地方呢?就是这个static的instance_是一个object而不是一个指针。这个带来什么问题呢?就是这个变量的初始化时间不是程序员决定的而是编译器决定的。这样的话一些其他的变量如果对之有依赖的话,就会引发一些没有定义的行为。比如说这里在其他文件中(
注意其他文件中)有另一个全局变量.
- int globala = Singleton::Instance()->DoSomething();
那这个globala在创建的时候就是未定义的。
当然这种行为只是未定义而不是一定是错的,就我用MS的编译器测试来看,instance_是可以被正确的初始化的,同时类似的行为用一个全部变量来初始化另一全局变量貌似问题也不大。(而其编译器都没有任何的warning!!!,但是不得不提一句,在C里面你要是想做这样的事情恐怕就是不罩的因为所谓要求右值是常量的限制
),在网上搜索了一下看到了一些的论述,大致的意思和书中类似,比如下面这个URL http://stevewetherill.com/2008/10/25/c-static-global-initialization/ 但是实际用这里的例子跑一便之后发现答案还是正确的,应该说我们尽力避免这样初始化依赖,但是同时我们相信我们的编译器也会越来越聪明:)
废话说了很多,回来原来的实现上来。所以原则上我们实现的模型归结为2个。
1) 指针模型
- static Singleton* pInstance_;
- static Singleton* Instance()
- {
- if (!pInstance_)
- pInstance_ = new Singleton;
- return pInstance_;
- }
2) 静态局部变量模型
- static Singleton* Instance()
- {
- static Singleton instance;
- return &instance;
- }
有了这两个模型之后,进一步我们要探索的是2个问题,一个是多线程的实际环境,另一个就是singleton的生命周期。
就生命周期我们有很深的体会,确实在很多应用程序开发的过程中碰到了诸如实例删除顺序依赖以及重新调用了已删除了实例的实例。这里面也给出了精彩的解法。
第一个就是所谓的Phoenix Singleton。援引原文的描述:
Just as the legendary Phoenix bird rises repeatedly from its own ashes, the Phoenix singleton is able to rise again after it has been destroyed.(这说的不是一辉吗:))这个Phoenix在被删除后重新使用时重新创建实例,然后调用atexit,希望系统帮助来删除实例。
- void Singleton::OnDeadReference()
- {
- Create();
- new(pInstance_) Singleton;
- atexit(KillPhoenixSingleton);
- destroyed_ = false;
- }
- void Singleton::KillPhoenixSingleton()
- {
- pInstance_->~Singleton();
- }
实现虽然在这里了,但是我依然很质疑他的原拆原建模型,不能肯定这个原先static的指针指向的内存会不会被系统回收掉,最近比较忙就不测试了,有机会一定好好搞一搞。
第二个生命周期的policy就是Longevity,不知道中文版把他翻译成什么总之很难翻。基本的思路就是每个singleton甚至全局变量都给上一个生命值,那在删除的时候就会依次进行,避免了顺序上的问题,同时也避免使用诸如dependency类一样的高耦合的模板,在这里我们就不展开了。
最后要说的就是多线程了,以前看过一个scott mayer的关于singleton多线程的讨论,这个模型的演变和作者的顺序如出一辙。
第一步就是没有多线程保护的版本,就是
- Singleton& Singleton::Instance()
- {
- if (!pInstance_)
- {
- pInstance_ = new Singleton;
- }
- return *pInstance_;
- }
第二步那高级了一点,进了函数之后就有一个lock的变量。
- Singleton& Singleton::Instance()
- {
- Lock guard(mutex_);
- if (!pInstance_)
- {
- pInstance_ = new Singleton;
- }
- return *pInstance_;
- }
但是这样带来的问题是效率不高,实际上guard只需要看护的就是一种情况,就是最初的创建singleton实例的这个调用语句。在已经创建好的环境下这样的guard已经没有太多的意义了。
所有这才有了第三步。
- Singleton& Singleton::Instance()
- {
- if (!pInstance_)
- {
- Guard myGuard(lock_);
- if (!pInstance_)
- {
- pInstance_ = new Singleton;
- }
- }
- return *pInstance_;
- }
把这个guard延后到了pInstance确实没有创建的时候,在效率上获得了不小的提高。(其实自己写个singleton哪有这么复杂啊,呵呵)
综上所述,我们最后又用到第一章的理论,用一个SingletonHolder把前面的那些模型用policy实现出来,这个SingletonHolder的定义如下:
- template
- <
- typename T,
- template <class> class CreationPolicy = CreateUsingNew,
- template <class> class LifetimePolicy = DefaultLifetime,
- template <class, class> class ThreadingModel = LOKI_DEFAULT_THREADING_NO_OBJ_LEVEL,
- class MutexPolicy = LOKI_DEFAULT_MUTEX
- >
- class SingletonHolder
- {
- public:
-
- typedef T ObjectType;
-
- static T& Instance();
-
- private:
-
- static void MakeInstance();
- static void LOKI_C_CALLING_CONVENTION_QUALIFIER DestroySingleton();
-
-
- SingletonHolder();
-
-
- typedef typename ThreadingModel<T*,MutexPolicy>::VolatileType PtrInstanceType;
- static PtrInstanceType pInstance_;
- static bool destroyed_;
- };
perfect!!