singleton模式的c++实现
Singleton 模式在C++中的一种实现
说明:本文介绍了Singleton模式在C++中的一种实现方式,此方式不但较好的解决了全部
Singleton对象析构的次序问题,也提出了一个解决Singleton派生类体系的方法。
------------------------------------------------------------------------------
--
by: 方泓
Last Updated: May-08-2002 | First Version:May-08-2002
------------------------------------------------------------------------------
--
介绍
在我们的编程实践中经常会遇到这样的情况,象Logger, MemoryManager,SystemInforma
tion等这些类在整个系统中只应存在一个实例,这时我们就可以使用Singleton模式来实现
这些类。Singleton是最基本最常使用的设计模式之一,很多模式可以使用Singleton模式
实现,如Abstract Factory, Builder, Prototype往往是通过Singleton来实现的。在经典
的GoF书[1]中它的Intent是这样定义的:Ensure a class only has one instance, and
provide a global point of access to it. (保证一个类仅有一个实例,并提供一个访问
它的全局访问点。)
Singleton不仅引入自然,使用简捷方便,而且也是解决C++中non-local static objects
无法以某种正确次序初始化的一个极好方案。这在GoF[1]书中有所介绍,在Scott Meyers
的 Effective C++[2] 书中的Item 47,"Ensure that non-local static objects are
initialized before they're used. " 中有关于此点更为详细的说明。若非如此,可能需
要一些不够优美的方法来解决这个问题,在MFC的实现源码中我们可以看到有许多#pragma
init_seg(lib), 就能部分解决全局变量初始化的次序问题,但这种解决不但难看,而且
不是C++标准的一部分,因而是不可移植的。
实现
不奇怪,Singleton在C++中也有无数种实现方法,也各有不同的优缺点和适用范围,有些
可能很简单,也有些会很复杂,不过实现的基础一般都是采用local static object方法来
实现,好处在Effective C++ Item 47中有较详细的说明[2]。
在这篇中我将介绍一种我现在一直在使用的一种实现。
由于Singleton的类之间的可能存在依赖关系,所以在很多时候析构的次序也很重要。Evg
eniy Gabrilovich 在C++ Report上有一篇文章[3]较好的解决了这个问题,Evgeniy Gabr
ilovich引入了一个Singleton的析构管理类,每个singleton的类在注册到这个析构管理器
时都带有一个phase值,phase值越小的Singleton实例,则应该越晚被析构。在析构各个注
册的Singleton类实例时先根据Singleton类的phase值排序后再进行析构操作,这样只要各
Singleton中不出现循环的依赖关系,这种方法就是可行的,更详细的介绍可见[3]。
我对此方法做一些扩展,下面我结合实现代码来进行说明。
在此定义里我们可以看到TSingleton的constructor和destructor是protected的,这是因
为我们不想让TSingleton单独使用,而是让它做为Singleton类的基类来使用。
T::sc_nClassPhase为Singleton类的一个静态常量,这有点象类的ID,但并不和ID完全一
样,后面还会有更详细的说明。
这个类很简单,可能你唯一奇怪的地方就是TDestructor和CDestructionManager了。我们
接着看看它们的实现:
这里CDestructionManager类就是我们前面提到过的Singleton实例析构的管理类,这里我
们可以看到一个有趣的现象,CDestructionManager也是一个从TSingleton派生出来的对象
,很容易理解,因为它只应该有唯一实例,所以应该是一个Singleton类。它需要自己管理
自己,要注意它析构时不能漏了自己,而且它应该最后被析构,所以我给它分配了系统最
小的phase值,只要保证最小就行,在这里我定为此phase值整数1。可以看到在CDestruct
ionManager类中维护一个CDestructor指针数组,用于系统每一个注册的Singleton类实例
的析构。CDestructor中有一个虚函数Destroy()就是起这个作用的,它是一个纯虚函数,
在CDestructor的派生类TDestructor中来正确实现析构代码。看TDestructor的实现就知道
它是一个非常简单的template类。代码中的SDestroyObjects的唯一作用就是在系统结束时
自动调用DestroyObjects(),从DestroyObjects()的实现可以看出它先根据Singleton类的
phase值进行排序,然后对数组中每一个元素调用Destroy()。SDestroyObjects和Destroy
Objects()都设成private的作用是阻止编程者的自己调用。
由于CDestructionManager本身也是一个Singleton类,所以我们还可以从中掌握如何使用
TSingleton类,需要注意的是两个宏M_DefineSingleton和M_RegisterTypeOfSingletonPh
ase。
用了macro总会使代码更难懂一些,但比每次使用键入一些相同的东西要好一些。在这里我
说明一下,由Singleton类的特点所知不应该有copy constructor 和 assignment operat
or。M_Noncopyable就是起这个作用的。 BOOST_STATIC_CONSTANT定义了我在前面说明过的
Singleton类的静态常量phase值, 定义了friend class TDestructor 和 TSingleton是因
为Singleton类的定义中往往会将构造函数定义成非public的。 using baseclass::Insta
nce这句是因为我们使用了private继承,所以Instance()在派生类(我们要实现的Single
ton类)中也是private的,在使用MySingleton::Instance()这样的语句时就会产编译错误
,这时使用using这句话可以用来解决这个问题。 剩下来唯一的疑问可能就是M_TypeOfID
了,它也是一个宏,我们来看看它的实现:
这里利用了template specialization技术来实现整数和一个类的一一对应关系,TType2T
ype具体方法可见 Andrei Alexandrescu 发表在CUJ上的文章[4]。这段代码的目的有两个
,一是防止同一个ID对应两个Type,另一个是只要在编译时期知道ID,就可以得到对应的
Type,这两条都很重要,第一条可以阻止一些错误的发生,如果两个Singleton类使用同一
个phase值,在编译M_RegisterTypeOfSingletonPhase时就会报错,第二个的体现就是我们
已经在使用的宏M_TypeOfID。
再回头看看TSingleton类的Instance()的实现,
static T* s_pInstance = new M_TypeOfSingletonPhase(T);
这里可能和其他人的写法不太一样,我并没有写成:
static T* s_pInstance = new T;
这是我将phase值移到类的静态常量后的一个好处,可以用此方法来实现派生类体系的Sin
gleton,要点是在派生类中不要重新定义类phase值,而在利用M_RegisterTypeOfSinglet
onPhase时,只注册派生类。
关于本实现的一个例程可见Listing 1,这个例程的运行结果可见Listing 2。可以看出ph
ase值对析构次序所起的作用。CLogger的实例在CResource的实例析构后才析构,这是因为
我们定义了 CLogger的phase值为500, 而CResource的phase值为501 。仔细看运行结果,
我们还可以看出virtual函数Log()所起的作用。
总结
本文介绍了Singleton的一种实现方法,可以以正确的次序析构存在依赖关系的一组Singl
eton实例。我在这的实现主要是基于Evgeniy Gabrilovich的方法,改进了一些功能和使用
方便性,并提出一种有派生关系的Singleton类组合的解决方法。这种Singleton的实现方
法目前在我的代码中广泛使用。
不过要注意这种解决方法并非是 thread-safe的, 关于thread-safe的singleton 实现,可
以利用boost.thread来比较方便的实现,也许以后我会再加以介绍的。
这种方法还有一个缺点就是有时需要手工调整phase值,这样会引起部分代码重新编译,所
以在设置phase值时可以适当留空,如定义成 10, 20这样,以后若中间要加一个值,可选
为15。
关于本文你有什么想法,please feel free to contact me。
注释
[1] Gamma, Helm, Johnson, Vlissides. Design Patterns: Elements of Reusable Obj
ect-Oriented Software (Addison Wesley, 1995). (中译本) 设计模式:可复用面向对象
软件的基础 (机械工业出版社, 2000).
[2] Scott Meyers. Effective C++: 50 Specific Ways to Improve Your Programs and
Designs (Addison-Wesley Longman, 1998). (中译本) Effective C++ 中文版 2nd Edi
tion (华中科技大学出版社, 2001).
[3] Evgeniy Gabrilovich. "Destruction-Managed Singleton: A Compound Pattern fo
r Reliable Deallocation of Singletons", C++ Report, March 2000 .
[4] Andrei Alexandrescu. "Mappings between types and values.", C/C++ Users Jou
rnal, 18(10), 2000.
附录
Listing 1
- Gamma, Helm, Johnson, Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison Wesley, 1995). (中译本) 设计模式:可复用面向对象软件的基础 (机械工业出版社, 2000).
- Scott Meyers. Effective C++: 50 Specific Ways to Improve Your Programs and Designs (Addison-Wesley Longman, 1998). (中译本) Effective C++ 中文版 2nd Edition (华中科技大学出版社, 2001).
- Evgeniy Gabrilovich. "Destruction-Managed Singleton: A Compound Pattern for Reliable Deallocation of Singletons", C++ Report, March 2000 .
- Andrei Alexandrescu. "Mappings between types and values.", C/C++ Users Journal, 18(10), 2000.