当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++已经保证了静态成员变量创建的线程安全性,所以再无讨论这种方式的必要了
|
作者:逆向人 公众号:逆向人 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。 |

浙公网安备 33010602011771号