Singleton模式的C++实现研究(转贴)

本文聚焦Singleton模式的C++实现,提出基于模板函数、模板类、自身静态成员函数的三种实现方式,并分析其优缺点。同时指出《Design Patterns》上实现的缺陷并给出改进代码,还探讨了对象销毁及delete操作的处理。

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

Singleton模式的C++实现研究<o:p></o:p>

 <o:p></o:p>

张友邦<o:p></o:p>

 <o:p></o:p>

  要 本文提出了三种Singleton模式的实现方式,并做了对比分析。<o:p></o:p>

关键字 设计模式,Singleton<o:p></o:p>

 <o:p></o:p>

    Singleton(单件)模式是一种很常用的设计模式。《Design Patterns》对它作的定义为:Ensure a class only has one instance, and provide a global point of access to it. 也就是说单件类在整个应用程序的生命周期中只能有一个实例存在,使用者通过一个全局的访问点来访问该实例。这是Singleton的两个最基本的特征,也是在实现的时候首先应该考虑的。Singleton的应用很广,它可以典型的被用来表示那些本性上具有唯一特性的系统组件,如数据库访问组件等。这一点在《Design Patterns》上有详细说明,在此就不细说了。<o:p></o:p>

实现Singleton有很多途径,但都离不开两条最基本的原则。首先,要使得Singleton只有一个全局唯一的实例,我们通常的做法是将它的构造函数和拷贝构造函数私有化。再者,Singleton的全局唯一实例通常是一个static变量,这一点利用了语言的内在优势。本文给出的几种实现都比较简单,容易理解。在通常的情况下,它们足以满足要求。但缺点也是不可避免,以下我们逐一分析。<o:p></o:p>

 <o:p></o:p>

一、基于模板函数的实现<o:p></o:p>

 <o:p></o:p>

先看实现代码:<o:p></o:p>

class MySingleton1<o:p></o:p>

{<o:p></o:p>

private:<o:p></o:p>

    MySingleton1(){ cout << _T("Construct MySingleton1") << endl; }<o:p></o:p>

    MySingleton1(const MySingleton1&){} //拷贝构造函数<o:p></o:p>

MySingleton1 & operator =(const MySingleton1&){} //赋值函数<o:p></o:p>

    template <typename T><o:p></o:p>

    friend T& GetInstanceRef();<o:p></o:p>

 <o:p></o:p>

public:<o:p></o:p>

    ~MySingleton1(){ cout << _T("Destroy MySingleton1") << endl; }<o:p></o:p>

 <o:p></o:p>

public:<o:p></o:p>

    void DoSomething(){ cout << _T("Do something here in MySingleton1") << endl; }<o:p></o:p>

};<o:p></o:p>

template <typename T><o:p></o:p>

T& GetInstanceRef() //返回全局唯一对象的一个引用<o:p></o:p>

{<o:p></o:p>

    static T _instance;<o:p></o:p>

    return _instance;<o:p></o:p>

}<o:p></o:p>

template <typename T><o:p></o:p>

T* GetInstancePtr() //返回全局唯一对象的指针<o:p></o:p>

{<o:p></o:p>

    return &GetInstanceRef<T>();<o:p></o:p>

}<o:p></o:p>

上面的代码中,MySingleton1是需要单实例化的类。下面的模板函数template <typename T> T& GetInstanceRef()返回该类的唯一实例(静态变量_instance)的一个引用,另一个模板函数调用它返回该实例的指针。我们可以注意到以下几点:<o:p></o:p>

1.    MySingleton1的构造函数私有,防止了程序员随意构造它的实例。<o:p></o:p>

2.    同样,拷贝构造函数MySingleton1(const MySingleton1&)也被声明为私有。<o:p></o:p>

3.    全局的模板函数template <typename T> T& GetInstanceRef()是MySingleton1的友元。因为MySingleton1的构造函数已经声明为私有,为了让GetInstanceRef能顺利的构造静态变量_instance,我们不得不将它声明为MySingleton1的友元函数。<o:p></o:p>

 <o:p></o:p>

这样,我们的类MySingleton1就具有了Singleton特性了,而全局访问点就是两个模板函数。测试代码如下:<o:p></o:p>

MySingleton1* myobj1;<o:p></o:p>

myobj1 = GetInstancePtr<MySingleton1>();<o:p></o:p>

myobj1->DoSomething();<o:p></o:p>

GetInstanceRef<MySingleton1>().DoSomething();<o:p></o:p>

    下面我们分析这种实现的缺点。由于模板函数GetInstanceRef被特化后要访问MySingleton1,它的声明必须在类(MySingleton1)声明之后(区分声明与实现),这与我们通常的使用方式不合。虽然它在其它方面表现的比较良好,但就这一个缺点已经使我不会再想使用它了。来看第二种可以实际使用的实现。<o:p></o:p>

 <o:p></o:p>

二、基于模板类的实现<o:p></o:p>

 <o:p></o:p>

    这种实现的基本思路是,做一个类让它来负责提供Singleton对象的生成与访问。由于它要构造Singleton对象,所以让它成为一个友元是理所当然的。下面看看实现代码:<o:p></o:p>

template <typename T><o:p></o:p>

class SingletonWraper<o:p></o:p>

{<o:p></o:p>

public:<o:p></o:p>

static T& GetInstanceRef()<o:p></o:p>

{<o:p></o:p>

     static T _instance;<o:p></o:p>

     return _instance;<o:p></o:p>

}<o:p></o:p>

static const T& GetInstanceConst()<o:p></o:p>

{<o:p></o:p>

     return GetInstanceRef();<o:p></o:p>

}      <o:p></o:p>

static T* GetInstancePtr()<o:p></o:p>

{<o:p></o:p>

     return &GetInstanceRef();<o:p></o:p>

}<o:p></o:p>

};<o:p></o:p>

#define DEFINE_SINGLETON(ClassName); \<o:p></o:p>

public: \<o:p></o:p>

friend class SingletonWraper<ClassName>; \<o:p></o:p>

typedef class SingletonWraper<ClassName> SingletonWraper; \<o:p></o:p>

typedef SingletonWraper SingletonInterface; \<o:p></o:p>

private: \<o:p></o:p>

ClassName(const ClassName&){} \<o:p></o:p>

ClassName& operator=(const ClassName&) \<o:p></o:p>

{ \<o:p></o:p>

     return SingletonInterface::GetInstanceRef(); \<o:p></o:p>

} \<o:p></o:p>

private: //End of define DEFINE_SINGLETON(ClassName);<o:p></o:p>

class MySingleton2<o:p></o:p>

{<o:p></o:p>

DEFINE_SINGLETON(MySingleton2);<o:p></o:p>

private:<o:p></o:p>

MySingleton2(){ cout << _T("Construct MySingleton2") << endl; }<o:p></o:p>

 <o:p></o:p>

public:<o:p></o:p>

~MySingleton2(){ cout << _T("Destroy MySingleton2") << endl; }<o:p></o:p>

 <o:p></o:p>

public:<o:p></o:p>

void DoSomething(){ cout << _T("Do something here in MySingleton2") << endl; }<o:p></o:p>

};<o:p></o:p>

    先看看SingletonWraper类,它提供的三个静态函数用于取得对Singleton对象的访问。再看下面的一个宏,它的作用是声明友元以及定义两个Singleton对象的访问点(SingletonWraper和SingletonInterface),并且,它还重载了拷贝构造函数以使访问的Singleton对象永远都是由GetInstanceRef惰性生成的一个实例。我们可以看见,使用这个SingletonWraper来包装Singleton类已经变得很简单了。我们只需要在需要Singleton化的类里面声明一条语句DEFINE_SINGLETON(MySingleton2);就可以了。但这还是有一些前提的,如构造函数(包括拷贝构造函数)私有以及析构函数公有。测试代码如下:<o:p></o:p>

    MySingleton2* myobj2;<o:p></o:p>

    myobj2 = SingletonWraper<MySingleton2>::GetInstancePtr();<o:p></o:p>

    myobj2->DoSomething();<o:p></o:p>

    MySingleton2::SingletonInterface::GetInstanceRef().DoSomething();<o:p></o:p>

 <o:p></o:p>

三、基于自身静态成员函数的实现<o:p></o:p>

 <o:p></o:p>

    这个实现不比前面的实现复杂,相反,更简单了。思路是从要实现Singleton的类自身入手,实现它的静态成员函数来提供全局的实例访问。这个实例的构造也是发生在它内部的一个静态成员函数里,所以,我们不用再使用友元来提供额外的访问权限。并且,我们也没有再使用任何模板。代码如下:<o:p></o:p>

#define DECLARE_SINGLETON(ClassName); \<o:p></o:p>

public: \<o:p></o:p>

static ClassName& GetInstanceRef() \<o:p></o:p>

{ \<o:p></o:p>

     static ClassName _instance; \<o:p></o:p>

     return _instance; \<o:p></o:p>

} \<o:p></o:p>

static const ClassName& GetInstanceConst() \<o:p></o:p>

{ \<o:p></o:p>

     return GetInstanceRef(); \<o:p></o:p>

} \<o:p></o:p>

static ClassName* GetInstancePtr() \<o:p></o:p>

{ \<o:p></o:p>

     return &GetInstanceRef(); \<o:p></o:p>

} \<o:p></o:p>

private: \<o:p></o:p>

ClassName(const ClassName&){} \<o:p></o:p>

ClassName& operator=(const ClassName&) \<o:p></o:p>

{ \<o:p></o:p>

     return GetInstanceRef(); \<o:p></o:p>

} \<o:p></o:p>

private: \<o:p></o:p>

static void operator delete(void *p, size_t n) \<o:p></o:p>

{ \<o:p></o:p>

     ; /* 嘿嘿,什么都不要做.<o:p></o:p>

         但要注意,析构函数已经执行。<o:p></o:p>

          但对象并没有真正从内存卸载掉。*/ \<o:p></o:p>

}//End of define DECLARE_SINGLETON(ClassName);<o:p></o:p>

 <o:p></o:p>

class MySingleton3<o:p></o:p>

{<o:p></o:p>

DECLARE_SINGLETON(MySingleton3);<o:p></o:p>

 <o:p></o:p>

private:<o:p></o:p>

MySingleton3(){ cout << _T("Construct MySingleton3") << endl; }<o:p></o:p>

 <o:p></o:p>

public:<o:p></o:p>

~MySingleton3(){ cout << _T("Destroy MySingleton3") << endl; }<o:p></o:p>

 <o:p></o:p>

public:<o:p></o:p>

void DoSomething(){ cout << _T("Do something here in MySingleton3") << endl; }<o:p></o:p>

};<o:p></o:p>

实现Singleton的代码就一个宏定义而已,而使用它来使一个类拥有Singleton属性也只是调用一下这条宏。从使用上来看它应该是最简单的,看看下面的测试代码:<o:p></o:p>

MySingleton3 *myobj3 = MySingleton3::GetInstancePtr();<o:p></o:p>

myobj3->DoSomething();<o:p></o:p>

delete myobj3;<o:p></o:p>

MySingleton3::GetInstanceRef().DoSomething();<o:p></o:p>

对比这里的测试代码和上一个的,可以发现,在使用过程中,这种方式也是最简单的。前面的三种方式都是在栈空间中创建对象,对象的销毁是在作用域边界上。细心的读者可能已经发现问题了。如果我们得到对象的指针后把它给delete了,则肯定就出问题了。对第一二种实现,我们没有重载delete操作符,delete之后指针将不再可用。而对第三种实现,我们有delete的重载函数,它阻止了对象的真正卸载。但在执行delete函数之前,析构函数已经执行了,因为全局的delete操作首先调用的是类的析构函数,再调用类的delete重载操作符函数。汇编代码清楚的显示了这一点:<o:p></o:p>

MySingleton3::`scalar deleting destructor':<o:p></o:p>

00412270   push        ebp<o:p></o:p>

……<o:p></o:p>

0041228D   mov         ecx,dword ptr [ebp-4]<o:p></o:p>

00412290   call        @ILT+175(MySingleton3::~MySingleton3) (004010b4)<o:p></o:p>

00412295   mov         eax,dword ptr [ebp+8]<o:p></o:p>

00412298   and         eax,1<o:p></o:p>

0041229B   test        eax,eax<o:p></o:p>

0041229D   je          MySingleton3::`scalar deleting destructor'+3Dh (004122ad)<o:p></o:p>

0041229F   push        4<o:p></o:p>

004122A1   mov         ecx,dword ptr [ebp-4]<o:p></o:p>

004122A4   push        ecx<o:p></o:p>

004122A5   call        @ILT+70(MySingleton3::operator delete) (0040104b)<o:p></o:p>

004122AA   add         esp,8<o:p></o:p>

……<o:p></o:p>

前面的三个实现中Singleton的全局唯一对象是自动创建(惰性初始化)并自动销毁(在作用域边界上),而程序员非要执行delete操作的话将是错误的,这好比我们在程序中执行如下一段代码。<o:p></o:p>

int i(0);<o:p></o:p>

int* p = &i;<o:p></o:p>

delete p;<o:p></o:p>

很显然,这是不被允许的。对这一点最好的处理方式是在delete的时候抛出一个异常,因为我们不允许程序员在这里使用delete操作。考虑下面的代码:<o:p></o:p>

static void operator delete(void *p, size_t n) \<o:p></o:p>

{ throw –1; }<o:p></o:p>

相应的测试代码改为:<o:p></o:p>

try { delete myobj3; /*试着卸载对象*/ }<o:p></o:p>

catch(...) { cout << _T("Your object cannot be deleted.") << endl; /*失败*/ }<o:p></o:p>

 <o:p></o:p>

四、《Design Patterns》上的实现及其改进<o:p></o:p>

    在《Design Patterns ---Elements of Reusable Object-Oriented Software》(英文版)第127页讨论Singleton模式时也给出了一个实现,但它存在一个严重的缺陷:没有考虑对象的销毁。以下是它给出的Sample代码:<o:p></o:p>

class MazeFactory {<o:p></o:p>

public:<o:p></o:p>

static MazeFactory* Instance();<o:p></o:p>

 <o:p></o:p>

//existing interface goes here<o:p></o:p>

protected:<o:p></o:p>

MazeFactory();<o:p></o:p>

private:<o:p></o:p>

static MazeFactory* _instance;<o:p></o:p>

};<o:p></o:p>

 <o:p></o:p>

MazeFactory* MazeFactory::_instance = 0;<o:p></o:p>

 <o:p></o:p>

MazeFactory* MazeFactory::Instance() {<o:p></o:p>

if (_instance == 0) {<o:p></o:p>

    _instance = new MazeFactory;<o:p></o:p>

}<o:p></o:p>

return _instance;<o:p></o:p>

}<o:p></o:p>

    先分析一下它的实现策略。首先是构造函数访问受限(protected),然后声明了一个静态的对象指针,该指针的初始化(或者说该类的实例化)是在静态成员函数Instance里面。这里它并没有相应的对象卸载代码,然而在自由存储空间(堆空间)里生成的对象是不会自动卸载的。所以,经过改进,我得到了下面的代码。<o:p></o:p>

class MySingleton4<o:p></o:p>

{<o:p></o:p>

private:<o:p></o:p>

MySingleton4(){ cout << _T("Construct MySingleton4") << endl; } //构造函数私有    ~MySingleton4(){ cout << _T("Destroy MySingleton4") << endl; } //析构函数放哪里都可以了<o:p></o:p>

static MySingleton4* _instance;<o:p></o:p>

 <o:p></o:p>

public:<o:p></o:p>

static MySingleton4& GetInstanceRef()<o:p></o:p>

{<o:p></o:p>

     if (_instance == 0)<o:p></o:p>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值