本文介绍C++单例模式的集中实现方式,以及利弊
局部静态变量方式
1 | //通过静态成员变量实现单例 |
上述代码通过局部静态成员single实现单例类,原理就是函数的局部静态变量生命周期随着进程结束而结束。上述代码通过懒汉式的方式实现。
调用如下
1 | void test_single2() |
程序输出如下
1 | sp1 is 0x1304b10 |
确实生成了唯一实例,上述单例模式存在隐患,对于多线程方式生成的实例可能时多个。
静态成员变量指针方式
可以定义一个类的静态成员变量,用来控制实现单例
1 | //饿汉式 |
这么做的一个好处是我们可以通过饿汉式的方式避免线程安全问题
1 | //饿汉式初始化 |
程序输出如下
1 | s1 addr is 0x1e4b00 |
可见无论单线程还是多线程模式下,通过静态成员变量的指针实现的单例类都是唯一的。饿汉式是在程序启动时就进行单例的初始化,这种方式也可以通过懒汉式调用,无论饿汉式还是懒汉式都存在一个问题,就是什么时候释放内存?多线程情况下,释放内存就很难了,还有二次释放内存的风险。
我们定义一个单例类并用懒汉式方式调用
1 | //懒汉式指针 |
在cpp文件里初始化静态成员,并定义一个测试函数
1 | //懒汉式 |
函数输出如下
1 | this is lazy thread 0 |
此时生成的单例对象的内存空间还没回收,这是个问题,另外如果多线程情况下多次delete也会造成崩溃。
智能指针方式
可以利用智能指针自动回收内存的机制设计单例类
1 | //利用智能指针解决释放问题 |
SingleAuto的GetInst返回std::shared_ptr
在主函数调用如下测试函数
1 | // 智能指针方式 |
程序输出如下
1 | sp1 is 0x1174f30 |
智能指针方式不存在内存泄漏,但是有一个隐患就是单例类的析构函数时public的,如果被人手动调用会存在崩溃问题,比如将上边test_singleauto中的注释打开,程序会崩溃。
辅助类智能指针单例模式
智能指针在构造的时候可以指定删除器,所以可以传递一个辅助类或者辅助函数帮助智能指针回收内存时调用我们指定的析构函数。
1 | // safe deletor |
SafeDeletor要写在SingleAutoSafe上边,并且SafeDeletor要声明为SingleAutoSafe类的友元类,这样就可以访问SingleAutoSafe的析构函数了。
我们在构造single时制定了SafeDeletor(),single在回收时,会调用SingleAutoSafe的仿函数,从而完成内存的销毁。
并且SingleAutoSafe的析构函数为私有的无法被外界手动调用了。
1 | //智能指针初始化为nullptr |
程序输出如下
1 | sp1 is 0x1264f30 |
通过辅助类调用单例类的析构函数保证了内存释放的安全性和唯一性。这种方式时生产中常用的。如果将test_singleautosafe函数的注释打开,手动delete sp1.get()编译阶段就会报错,达到了代码安全的目的。因为析构被设置为私有函数了。
通用的单例模板类
我们可以通过声明单例的模板类,然后继承这个单例模板类的所有类就是单例类了。达到泛型编程提高效率的目的。
1 | template <typename T> |
我们定义一个网络的单例类,继承上述模板类即可,并将构造和析构设置为私有,同时设置友元保证自己的析构和构造可以被友元类调用.
1 | //通过继承方式实现网络模块单例 |
在主函数中调用如下
1 | void test_singlenet() |
程序输出如下
1 | sp1 is 0x1164f30 |
总结
本文介绍了一些面试常见问题
源码链接
https://gitee.com/secondtonone1/cpplearn
想系统学习更多C++知识,可点击下方链接。
C++基础