当NONCOPYABLE遇见SINGLETON

在实现单例类时,通常要把构造相关的几个函数访问权限设为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++已经保证了静态成员变量创建的线程安全性,所以再无讨论这种方式的必要了

猜你喜欢

转载自blog.csdn.net/qingzhuyuxian/article/details/108485174
今日推荐