在实现单例类时,通常要把构造相关的几个函数访问权限设为private或protected(最好是private)。但假设一个大型系统中,有数十个单例类(这很正常,单例类其实是外观模式的一种最常用设计),每一个都这么写显得繁琐不堪。要把这些操作代表的代码复用,可以使用c++语言提供的利器---继承。
设计如下的基类:
1 class NonCopyable {
2 public:
3 NonCopyable() = default;
4 NonCopyable(const NonCopyable&) = delete;
5 void operator=(const NonCopyable& c) = delete;
6 };
当单例类继承此类后,客户就无法在复制或赋值此单例类的实例化对象了,这样整个内存只存在一个全局对象。这里的NonCopyable类实现参考了boost::noncopyable。
解决了构造函数访问权限问题后,还有个问题,就是如何实例化?单例类实现的经典做法是在类里声明一个私有的静态类对象的指针,使用get手法返回此对象实例引用,通过此引用再调用类的其它方法。很好,但每个单例类都要这么实现一遍仍然繁琐。此时可以使用C++提供的另一个代码复用的利器--template。先实现一个通用的singleton模板类,模板类型参数传入待实现的单例类名,这样在编译阶段就完成了单例类的构造(模板实例化)。
通用的singleton模板类实现如下:
1 template <class T>
2 class Singleton : public NonCopyable {
3 private:
4 static T* inst_;
5
6 public:
7 Singleton() {}
8 virtual ~Singleton() {}
9
10 static T& inst()
11 {
12 if (!inst_) inst_ = new T;
13 return *inst_;
14 }
15
16 static void uninst()
17 {
18 if (!inst_) return;
19 delete inst_;
20 inst_ = nullptr;
21 }
22
23 };
24 //__declspec(selectany)声明使得我们可以在头文件中初始化一个全局变量
25 template <class T> __declspec(selectany) T * Singleton<T>::inst_ = nullptr;
自定义单例类实现:
1 class singletontest :public Singleton<singletontest>
2 {
3 public:
4 singletontest(){ printf("singletontest constructor function called "); }
5 ~singletontest(){ printf("class test object destroyed "); }
6 //成员方法
7 void print(){
8 printf("singletontest::print function called ");
9 }
10 };
测试代码:
1 singletontest::inst().print(); //line1
2 singletontest a; //line 2 无法阻止默认构造,理想中,单例类只允许出现类似line1的调用
3 printf("singletontest obj a addr is %d\n", &a);
4 a.print();
5 singletontest b;
6 printf("singletontest obj b addr is %d\n", &b);
7 b.print();
8 //a(b); //singletontest继承了noncopyable,继承类初始化时先调用父类的构造函数,由于定义成私有,所以构造失败
9 //a = b; //同上,赋值运算操作符函数同样是私有的
测试结果:
line2行代码写完后编译期并不报错,意味着对象使用默认构造生成。想阻止这种行为,就要把默认构造函数设为私有,但inst()方法里的 new T就会失败,更加得不偿失。
测试 line 8、9代码的错误,编译器可以识别。
代码里存在三层的继承链(singletontest-> singleton<T>-> Noncopyable),但因为类中没有声明虚函数,调用方法上无周转之处,所以性能上没有任何损失。
singletontest:public sington<singletontest>这种写法成文CRTP(Curiously Recurring Template Pattern:奇异递归模板模式),常用于实现静多态,本文不作过多介绍。
最后,一般在多线程环境中讨论单例类创建时,常有饱汉式与饿汉式两种方式,但现代C++已经保证了静态成员变量创建的线程安全性,所以再无讨论这种方式的必要了