目前位置: VCer资源中心 >>> VCer文章 >>> C++/MFC基础

[本帖已阅读2458次 分值85 回复0次] 张贴资源 发回信箱 控制面板

singleton模式的C++实现

提供者:jerry 张贴时间:2004-03-09 22:11:24.0 出处:bbs白云黄鹤站 作者:不祥

singleton模式的C++实现(2004-03-09 22:11:24.0)


大头


 
级别: VCer师长
头衔: VCer创始人

经验: 22768
作品: 121
分会: 华中分会
注册: 2003-12-04 10:47:17.0
登录: 2007-06-23 10:17:58.0

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

  1. Gamma, Helm, Johnson, Vlissides. Design Patterns: Elements of Reusable Object-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 Edition (华中科技大学出版社, 2001).
  3. Evgeniy Gabrilovich. "Destruction-Managed Singleton: A Compound Pattern for Reliable Deallocation of Singletons", C++ Report, March 2000 .
  4. Andrei Alexandrescu. "Mappings between types and values.", C/C++ Users Journal, 18(10), 2000.

注:转载文章需注明来源:VCer.net 文章地址:http://vcer.net/1047219017272.html

  如果你觉得VCer.net不错,而且你愿意为VCer.net捐赠一元钱,那么点击后面的捐赠按钮吧:) vcer.net捐赠

[回复该贴] [加入个人书签]
[投票结果]

A: 评分 10 50% (1 票)
B: 评分 5 0% (0 票)
C: 评分 0 0% (0 票)
D: 评分 -5 50% (1 票)
E: 评分 -10 0% (0 票)