单例模式返回引用的生命周期问题及两种实现方案探讨
问题:返回引用的单例实例,其生命周期是否会超出返回引用的范围?
最近和同事讨论单例模式的实现时,发现我们的方案存在差异。我的实现代码如下:
class Singleton { public: static Singleton* getInstance() { if(!instance) instance = new Singleton(); return instance; } ~Singleton() { if(instance) delete instance; } private: Singleton() {} static Singleton* instance; };
同事的实现代码如下:
class Singleton { public: static Singleton& getInstance() { static Singleton instance; return instance; } private: Singleton() {} };
想请教一下:同事这个返回引用的单例实例,它的生命周期会不会超出返回引用的范围?
回答
放心,这个返回引用的单例实例绝对不会局限在getInstance()函数的范围内,它的生命周期会贯穿整个程序的运行周期,直到程序正常结束才会被销毁。
原因在于C++标准对函数内static局部变量的明确规定:
- 这类变量会在第一次调用包含它的函数时完成初始化,而非函数每次进入都重复初始化;
- 它的存储位置在静态存储区,和全局变量的存储特性一致,生命周期从初始化完成开始,到整个程序终止时才会被自动销毁。
所以getInstance()返回的引用,指向的是静态存储区里的instance对象——不管你在程序的哪个模块、哪个函数里调用getInstance()获取引用,只要程序还在运行,这个对象就会一直存在。
顺便提下你的指针版本单例的几个潜在问题,也能更凸显同事实现的优势:
- 析构逻辑错误:你的析构函数里
delete instance后,没有把instance指针置为nullptr,后续再调用getInstance()会访问已释放的内存,触发未定义行为;如果多个对象触发析构,还会导致重复释放问题。 - 线程安全隐患:多线程环境下,你的实现可能创建多个实例(
if(!instance)检查和new操作不是原子操作),而C++11及以后的标准中,函数内static局部变量的初始化是线程安全的,同事的版本天然规避了这个问题。 - 内存泄漏风险:如果程序异常退出,你的指针版本单例可能永远不会被释放;而同事的版本会由系统自动回收静态变量的内存,不存在泄漏问题。
总结来说,同事的这个返回引用的单例实现是更简洁、安全的经典写法,完全不用担心生命周期的问题。
内容的提问来源于stack exchange,提问作者VincentDM




