最近在cocos2d-x开发中经常会用到单例模式,而每个单例模式类实际上具备相似的基础结构,为了便于快速写出一个具有单例模式的类,可以借助模板或者宏。
1.单例模式的类实现
首先,给出一个基本的实现单例模式的代码:
class Singleton
{
public:
inline static Singleton& GetInstance()
{
static Singleton s_instance;
return s_instance;
}
private:
Singleton();
Singleton(Singleton const&){};
Singleton& operator= (Singleton const&){return *this;};
~Singleton();
};
#define SGL Singleton::GetInstance()
因为是用于cocos2d-x下的开发,而手机游戏很多时候仅需要单线程就足够了,因此此处没有考虑线程安全问题,如果有线程安全方面的考虑,则对此类稍加改动即可。另外GetInstance函数没有返回一个指针而是返回引用,是希望使用者像使用一个对象一样使用此类,而且这样的写法对于改写成模板或者宏都更容易,代码也更简洁。为了使用方便,为获取单例的函数写了一个宏,当然这个是可选项,不写也无所谓。
构造函数、拷贝构造函数、赋值操作符与析构函数全部设为私有,以防止类的使用者不小心对类重新创建和赋值,保证类有且仅有一个实例。
在单例模式中,拷贝构造函数与赋值操作符因为完全没有用,因此此处直接给出了空实现"{}",而构造函数和析构函数此处并未给出空实现,因为它们往往需要根据具体情况给出具体实现,而且构造函数和析构函数并不适合成为inline。
2.单例模式的模板实现
用模板实现单例模式实际上与基本类实现代码大同小异:
#ifndef SINGLETON_H_
#define SINGLETON_H_
template<typename T>
class Singleton
{
public:
inline static T& GetInstance()
{
static T s_instance;
return s_instance;
}
private:
Singleton();
Singleton(Singleton const&);
Singleton& operator= (Singleton const&);
~Singleton();
};
#endif//SINGLETON_H_
如上,大部分代码与普通的类实现没有区别。不过需要注意的是,除了多了一个template声明式之外,还有一个在文章中难以体现的区别是这种实现方式可能不需要cpp文件,尤其如果你和我一样用它做cocos2d-x开发的话,也不需要给出4个私有函数的实现。
使用模板时则类似于这样#include "Singleton.h"
class Example
{
};
typedef Singleton<Example> SglExample;
#define SGL_EXAMPLE SglExample::GetInstance()
将需要成为单例模式的类嵌入模板即可,而Example类的代码与普通类没有任何区别。
示例代码的最后多出了一个类别名(typedef语句)和一个宏(#define),作用是方便使用者使用单例,而不需要写重复代码,尤其是模板风格代码--实在算不上优美。
需要注意的是,这里的实现思路是将Example类作为了Singleton模板的“实现素材”,真正的单例是Singleton<Example>而不是Example,关于此设计的缺点,会在稍后详述。3.单例模式的宏实现
宏实现的代码实际上与类实现的代码依旧思路相同:
#ifndef SINGLETON_DIFINE_H_
#define SINGLETON_DIFINE_H_
//单例模式宏
#define SINGLETON(_CLASS_) \
public: \
inline static _CLASS_& GetInstance() \
{ \
static _CLASS_ s_instance; \
return s_instance; \
} \
private: \
_CLASS_(); \
_CLASS_(_CLASS_ const&){} \
_CLASS_& operator= (_CLASS_ const&){return *this;} \
~_CLASS_(); \
//单例模式默认构造函数与析构函数(配合单例模式宏使用)
#define SINGLETON_C_D(_CLASS_) \
_CLASS_::_CLASS_(){} \
_CLASS_::~_CLASS_(){} \
#endif //SINGLETON_DIFINE_H_
宏的实现与模板实现具有一些相似的特征,比如只需要.h文件。但又有很多区别,比如在宏定义中需要给出拷贝构造函数和赋值操作符的空实现,否则宏的使用者将必须手动实现这两个不会被也不该被使用的函数,而构造函数与析构函数的定义权限,依旧保留给宏的使用者,如果使用者不希望手动定义这两个函数,也可以在cpp文件中用配套的宏SINGLETON_C_D来帮助自己节省一些力气。
使用单例模式宏时则类似这样:
#include "SingletonDefine.h"
class Example
{
SINGLETON(Example);
};
#define SGL_EXAMPLE Example::GetInstance()
由于不会产生模板风格的代码,因此省去了一个typedef。类使用方法与普通手写的单例模式类(如1中所述那样的类)没有任何区别。
4.三种实现方式的分析
三种实现方式中类实现的可读性最好--当然对于这么几行代码,其实可读性对于任何人应该都不成问题。除此之外类实现的方式依赖性也最低,这里指的依赖性不是类之间的依赖性而是文件依赖性,即当代码文件被迁移到其他项目中去时,不需要额外附带一个单例模式.h文件(无论是模板的.h还是宏的.h),缺陷显而易见,当单例类比较多时,需要重复写很多思路相同的代码。当写一些可能被迁移的基础类时--如底层数据管理类,可以考虑选用这种方式。
下面比较模板实现方式和宏实现方式。两种实现都会导致代码膨胀,但机制却并不相同,宏定义的替换属于预处理,实际上是发生在编译前,而模板实现则在编译期生成实际代码,当然对于类的使用者来说并不会感知这种区别,尤其是有对应的typedef和define的帮助下。
但是,两种实现方式依然有其他一些区别,这些区别足以影响应该选择哪一种方式。
在模板实现一段中提到实际上作为“实现素材”的类Example并非单例,实际的单例是Singleton<Example>,也就是说使用模板实现将不可避免的在项目中产生两个功能几乎一样的类,一个是作为实际使用的单例,而另一个则是它的“影子”,一个除了做单例模板的素材之外,再不会被使用的类。这将产生一个隐患:难以避免类的使用者在无意中使用那个影子类。而影子类一旦被使用,则单例则被打破,会出现什么结果则难以预料。另一个更麻烦的问题是,如果Example中用到了枚举,则所有Example中的枚举都是属于Example类的而非Singleton<Example>,即使编译时生成了单例模式的代码,使用时却无法彻底摆脱原先的影子类。
宏实现没有模板实现的上述缺点,它不会产生“影子类”,类中如果有枚举甚至嵌套类也不会影响使用。当然,大多数时候在C++中是不推荐使用宏的,关于使用宏的隐患和缺点,相关文章很多,此处不再展开。这里要说的是,由于单例模式的实现宏使用范围非常狭窄,而且参数单一,因此,很多担忧的问题实际上并不容易发生。如果暂时搁置宏本身的问题,暂且只比较上面两种实现的话,宏实现明显的弱点是:宏定义比较丑,至少比模板定义丑,如果进行嵌套,丑陋指数加倍。
5.总结
优先使用宏实现。
可能频繁在多个项目中被迁移的代码,可以考虑使用最简单的类实现方式。
类中没有枚举,或者需要模板嵌套才能实现功能时,且能保证类使用者不会无意中使用“影子类”的情况下,使用模板实现。
6.补充
最近发现原来写的宏在vs下正常,而在eclipse下编译时会有警告,原因是拷贝构造函数未将所有成员(除了容器)进行初始化。在C++11中可利用委托构造函数消除此警告。方法是将_CLASS_(_CLASS_ const&) {}改为_CLASS_(_CLASS_ const&) : _CLASS_(){}即可。但是在C++03下除了自己写定义手动添加初始化列表之外我还未找到更好的办法。
另外赋值操作符虽然不会被调用,但定义中未写返回值,这也是不严谨的写法,尽管在一些编译器环境下可以通过编译,但在其他编译器上可能引起编译错误或者警告,因此也加入了返回值。
更改之后的完整代码如下(编译环境需支持C++11或以后的更新的版本规范):
#ifndef SINGLETON_DIFINE_H_
#define SINGLETON_DIFINE_H_
//单件类宏
#define SINGLETON(_CLASS_) \
public: \
inline static _CLASS_& GetInstance() \
{ \
static _CLASS_ s_instance; \
return s_instance; \
} \
private: \
_CLASS_(); \
_CLASS_(_CLASS_ const&) : _CLASS_(){} \
_CLASS_& operator= (_CLASS_ const&){return *this;} \
~_CLASS_();
//单件类的构造与析构函数(配合单件类宏使用)
#define SINGLETON_C_D(_CLASS_) \
_CLASS_::_CLASS_(){} \
_CLASS_::~_CLASS_(){} \
#endif //SINGLETON_DIFINE_H_==========
====更新记录====
2015-02-22:修正了部分代码排版问题和一些文字描述问题。
2015-04-27:修正了拷贝构造函数引起警告的问题,修正了赋值操作符定义的语法错误。
C++模板与宏实现单例模式解析
本文探讨了C++中实现单例模式的三种方法:基本类实现、模板实现和宏实现。详细解释了每种实现的优缺点,并提供了示例代码。模板实现可能导致代码膨胀和“影子类”问题,而宏实现虽然简洁,但可能引发宏的潜在问题。建议根据项目需求和代码迁移频率选择合适的方法。

2278





