`
javasee
  • 浏览: 961129 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

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

阅读更多

Singleton模式的C++实现研究<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

张友邦

本文提出了三种Singleton模式的实现方式,并做了对比分析。

关键字 设计模式,Singleton

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

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

一、基于模板函数的实现

先看实现代码:

class MySingleton1

{

private:

MySingleton1(){ cout << _T("Construct MySingleton1") << endl; }

MySingleton1(const MySingleton1&){} //拷贝构造函数

MySingleton1 & operator =(const MySingleton1&){} //赋值函数

template <typename T>

friend T& GetInstanceRef();

public:

~MySingleton1(){ cout << _T("Destroy MySingleton1") << endl; }

public:

void DoSomething(){ cout << _T("Do something here in MySingleton1") << endl; }

};

template <typename T>

T& GetInstanceRef() //返回全局唯一对象的一个引用

{

static T _instance;

return _instance;

}

template <typename T>

T* GetInstancePtr() //返回全局唯一对象的指针

{

return &GetInstanceRef<T>();

}

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

1. MySingleton1的构造函数私有,防止了程序员随意构造它的实例。

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

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

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

MySingleton1* myobj1;

myobj1 = GetInstancePtr<MySingleton1>();

myobj1->DoSomething();

GetInstanceRef<MySingleton1>().DoSomething();

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

二、基于模板类的实现

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

template <typename T>

class SingletonWraper

{

public:

static T& GetInstanceRef()

{

static T _instance;

return _instance;

}

static const T& GetInstanceConst()

{

return GetInstanceRef();

}

static T* GetInstancePtr()

{

return &GetInstanceRef();

}

};

#define DEFINE_SINGLETON(ClassName); \

public: \

friend class SingletonWraper<ClassName>; \

typedef class SingletonWraper<ClassName> SingletonWraper; \

typedef SingletonWraper SingletonInterface; \

private: \

ClassName(const ClassName&){} \

ClassName& operator=(const ClassName&) \

{ \

return SingletonInterface::GetInstanceRef(); \

} \

private: //End of define DEFINE_SINGLETON(ClassName);

class MySingleton2

{

DEFINE_SINGLETON(MySingleton2);

private:

MySingleton2(){ cout << _T("Construct MySingleton2") << endl; }

public:

~MySingleton2(){ cout << _T("Destroy MySingleton2") << endl; }

public:

void DoSomething(){ cout << _T("Do something here in MySingleton2") << endl; }

};

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

MySingleton2* myobj2;

myobj2 = SingletonWraper<MySingleton2>::GetInstancePtr();

myobj2->DoSomething();

MySingleton2::SingletonInterface::GetInstanceRef().DoSomething();

三、基于自身静态成员函数的实现

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

#define DECLARE_SINGLETON(ClassName); \

public: \

static ClassName& GetInstanceRef() \

{ \

static ClassName _instance; \

return _instance; \

} \

static const ClassName& GetInstanceConst() \

{ \

return GetInstanceRef(); \

} \

static ClassName* GetInstancePtr() \

{ \

return &GetInstanceRef(); \

} \

private: \

ClassName(const ClassName&){} \

ClassName& operator=(const ClassName&) \

{ \

return GetInstanceRef(); \

} \

private: \

static void operator delete(void *p, size_t n) \

{ \

; /* 嘿嘿,什么都不要做.

但要注意,析构函数已经执行。

但对象并没有真正从内存卸载掉。*/ \

}//End of define DECLARE_SINGLETON(ClassName);

class MySingleton3

{

DECLARE_SINGLETON(MySingleton3);

private:

MySingleton3(){ cout << _T("Construct MySingleton3") << endl; }

public:

~MySingleton3(){ cout << _T("Destroy MySingleton3") << endl; }

public:

void DoSomething(){ cout << _T("Do something here in MySingleton3") << endl; }

};

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

MySingleton3 *myobj3 = MySingleton3::GetInstancePtr();

myobj3->DoSomething();

delete myobj3;

MySingleton3::GetInstanceRef().DoSomething();

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

MySingleton3::`scalar deleting destructor':

00412270 push ebp

……

0041228D mov ecx,dword ptr [ebp-4]

00412290 call @ILT+175(MySingleton3::~MySingleton3) (004010b4)

00412295 mov eax,dword ptr [ebp+8]

00412298 and eax,1

0041229B test eax,eax

0041229D je MySingleton3::`scalar deleting destructor'+3Dh (004122ad)

0041229F push 4

004122A1 mov ecx,dword ptr [ebp-4]

004122A4 push ecx

004122A5 call @ILT+70(MySingleton3::operator delete) (0040104b)

004122AA add esp,8

……

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

int i(0);

int* p = &i;

delete p;

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

static void operator delete(void *p, size_t n) \

{ throw –1; }

相应的测试代码改为:

try { delete myobj3; /*试着卸载对象*/ }

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

四、《Design Patterns》上的实现及其改进

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

class MazeFactory {

public:

static MazeFactory* Instance();

//existing interface goes here

protected:

MazeFactory();

private:

static MazeFactory* _instance;

};

MazeFactory* MazeFactory::_instance = 0;

MazeFactory* MazeFactory::Instance() {

if (_instance == 0) {

_instance = new MazeFactory;

}

return _instance;

}

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

class MySingleton4

{

private:

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

static MySingleton4* _instance;

public:

static MySingleton4& GetInstanceRef()

{

if (_instance == 0)

_instance = new MySingleton4;

return *_instance;

}

static MySingleton4* GetInstancePtr()

{

return &GetInstanceRef();

}

static ReleaseInstance()

{

if (_instance != 0)

{

delete _instance;

_instance = 0;

}

}

public:

void DoSomething(){ cout << _T("Do something here in MySingleton4") << endl; }

};

MySingleton4* MySingleton4::_instance = 0; //Singleton对象初始化

static class DestructHelper //用于卸载MySingleton4对象的辅助类

{

public:

~DestructHelper(){ MySingleton4::ReleaseInstance(); }

} DestructHelperInstance; //辅助类静态实例

代码唯一的改进是增加了释放对象的静态函数ReleaseInstance。注意,在这个函数中_instance != 0判断以及后来的_instance = 0都是必不可少的,因为函数ReleaseInstance可能会被重复调用。将指针所指向的对象卸载后将指针置为0是一种非常好的编程习惯,它可以避免“野指针”的出现,而这通常是很危险的。接下来的类DestructHelper是用来辅助卸载MySingleton4的Singleton对象的,我们在它的公有析构函数里调用MySingleton4::ReleaseInstance()静态函数来完成工作。而DestructHelper的一个全局静态实例DestructHelperInstance会在适当的时候卸载掉,这就保证了析构函数~DestructHelper()得以被调用,从而卸载掉MySingleton4的Singleton对象。使用的时候,我们可以调用ReleaseInstance()手动的卸载掉对象,然后再调用GetInstanceRef()获得一个新的对象。如果我们不希望这样做,那么DestructHelperInstance的析构将保证堆空间里的对象得以被自动卸载。这样的处理方式增加了更多的灵活性。

但是,缺点还是有的。最主要的遗憾在于代码的重用上。第一、二种实现方式是基于模板技术的,代码可以很方便的重用,第三种方式虽然没有使用模板技术,但宏定义的使用也可以很好的保证代码的简单重用。最后的这种实现方式在代码重用上就显得稍微难了一点。我能想到的最佳解决办法仍然是使用宏(虽然我本人也很反对在C++里使用宏),一个用于定义MySingleton4类内部的处理,一个用于生成一个卸载辅助类的静态实例。第二个宏与模板有异曲同工的味道,不过,它还生成了一个与类型相关的静态变量。具体代码请见源程序清单。

五、结束语

本文全部代码在VC6.0里全部通过。

本文提出的只是作者写的几个比较简单的实现方式,但已经能满足大多数应用的需要了。更复杂的实现请参考http://www.hoversoft.net/devinfo/0205doc/20Singleton/index.htm。本文作者“油箱”:vcvbjava@yahoo.com。欢迎大家讨论,以求共同进步。

本文作者张友邦是微软认证专家,现在在湖南长沙高新技术开发区创智软件园创智数码科技有限公司从事开发工作。可以通过vcvbjava@yahoo.com和他取得联系。

分享到:
评论

相关推荐

    C++完美实现Singleton模式

    ### C++中实现Singleton模式的关键知识点 #### 一、Singleton模式简介 Singleton模式是一种常用的软件设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中经常被用于控制对共享资源...

    C++ 实现的singleton 模式

    **C++实现的Singleton模式详解** Singleton模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下都非常有用,例如管理共享资源,如数据库连接池,或者确保某个...

    Head First 设计模式 (五) 单件模式(Singleton pattern) C++实现

    在C++中实现单例模式有多种方法,下面我们将详细介绍几种常见的实现方式: 1. **静态成员变量法**: 这是最常见的一种实现方式,通过将实例声明为类的静态成员变量来保证只有一个实例存在。例如: ```cpp class ...

    c++实现单件模式 c++实现单件模式 c++实现单件模式

    在C++中实现单件模式,通常有几种常见的方式: 1. **静态成员变量法**: 这是最简单的实现方式。在单例类中声明一个静态成员实例,并在类内部提供一个静态的获取实例的方法。这样,当第一次调用该方法时,会创建一...

    C++实现Singleton单例模式

    C++实现Singleton单例模式 本文档将详细介绍如何使用C++语言实现设计模式中的单例模式。单例模式是一种常用的设计模式,它可以确保一个类只能实例化一次。 单例模式的定义: 单例模式是一种创建型模式,它可以确保...

    单例实现源码singleton-C++

    在C++中实现单例模式有多种方法,每种方法都有其优缺点。以下是对"单例实现源码singleton-C++"的详细解析。 1. **静态成员变量法** 这是最常见的单例实现方式。在类中定义一个静态成员变量,该变量保存唯一的实例...

    设计模式C++学习之单例模式(Singleton)

    在C++中,实现单例模式有多种方法,我们将会深入探讨这一模式的原理、优缺点以及如何在实际编程中应用。 单例模式的核心在于控制类的实例化过程,防止多处代码创建多个实例导致资源的浪费或者状态不一致的问题。在...

    C++CLI中实现singleton模式

    双重检测锁(Double-Checked Locking)实现的Singleton模式在多线程应用中有相当的价值。在ACE的实现中就大量使用ACE_Singleton模板类将普通类转换成具有Singleton行为的类。这种方式很好地消除了一些重复代码臭味,...

    singleton设计模式java实现及对比

    在Java中,Singleton模式的实现有多种方式,每种方式都有其优缺点,我们将详细探讨这些实现方法并进行对比。 ### 1. 饿汉式(Static Final Field) 这是最简单的Singleton实现方式,通过静态初始化器在类加载时就...

    单例模式 C++ 实现

    在C++中实现单例模式,通常有几种常见的方法: 1. **懒汉式**:也称为延迟初始化,只有当第一次调用单例对象时才创建实例。这种做法避免了在程序启动时就创建不必要的对象。不过,如果多个线程同时尝试创建实例,...

    Singleton模式源程序

    在C++中,Singleton模式的实现通常涉及以下关键点: 1. 私有化构造函数:Singleton类的构造函数通常声明为私有的,防止其他类通过new操作符直接创建实例。 2. 单例实例的静态成员变量:这个静态成员变量保存...

    Java的Singleton模式代码(免资源分)

    在给出的描述中提到“Java的Singleton模式代码”,这提示我们主要关注于如何通过不同的实现方式来创建一个符合Singleton模式的Java类。接下来,我们将详细探讨几种常见的Singleton实现方式及其优缺点。 #### 二、...

    C++实现的单例模式代码

    在C++中,实现单例模式通常涉及到内存管理、线程安全以及生命周期控制等问题。下面将详细探讨C++中实现单例模式的三种方法,以及在多线程环境下的考虑。 1. 静态成员变量法(单线程) 这是最简单的单例实现方式,...

    使用C++11实现线程安全的单例模式

    C++11引入了新的特性,如std::mutex和std::call_once,使得实现线程安全的单例模式变得更加容易和高效。 首先,我们需要理解C++11中的线程模型。在C++11之前,C++标准并不直接支持多线程编程。C++11引入了 `...

    最简单的设计模式学习Singleton模式

    ### 最简单的设计模式...通过私有构造函数、静态成员变量和静态工厂方法的组合使用,可以轻松地在Java和C++等语言中实现Singleton模式。同时,在多线程环境下,需要特别注意线程安全问题,采取相应的措施确保单例性。

    设计模式精解-GoF23种设计模式解析附C++实现源码

    C++实现源码提供了具体编程环境下的应用实例,帮助读者更好地理解和应用这些设计模式。通过阅读和实践这些源码,开发者可以更深入地掌握如何在实际项目中灵活运用设计模式,提升代码质量和可维护性。

    23种设计模式c++实现代码

    这个资源包含23种经典设计模式的C++实现,对于学习和理解C++编程以及提升软件设计能力非常有帮助。下面,我们将详细介绍这23种设计模式,并结合C++语言特性进行解析。 1. 单例模式(Singleton):保证一个类只有一...

    GoF+23种设计模式解析附C++实现源码(2nd+Edition).pdf

    **C++实现**: Singleton模式的经典实现是使用私有构造函数和静态成员函数来控制单例的创建和访问。现代C++推荐使用线程安全的懒汉式单例模式来避免不必要的资源初始化。 #### 1.4 Builder模式 **定义**: Builder...

Global site tag (gtag.js) - Google Analytics