如何正确检查单例实例是否已实例化?附两种实现的疑问
单例实例检查的正确方式 & 0 vs nullptr的疑问解答
你观察得很细致!这两个示例里的写法其实是C++不同发展阶段的编程习惯,我们一步步拆解清楚:
为什么用0而不是nullptr?
- 历史兼容性原因:在C11标准正式引入
nullptr之前,C里没有专门的"空指针常量",开发者普遍用整数0或者宏NULL来表示空指针。很多旧代码或者需要兼容C++03及更早版本的项目,会保留这种写法。 - 隐式转换特性:在C中,整数
0可以隐式转换为任意指针类型的空指针值,所以instance == 0和instance == nullptr在功能上是等价的。但nullptr的语义更清晰——它明确是指针类型的空值,不会和整数0混淆,所以在C11及以后的代码里,更推荐用nullptr。
为什么用!p_inst而不是显式比较?
这是利用了C++的指针布尔转换规则:空指针会被隐式转换为false,非空指针则转换为true。所以!p_inst完全等价于p_inst == nullptr(或p_inst == 0),这种写法更简洁,但可读性见仁见智——如果团队追求代码的明确性,显式写p_inst == nullptr会更易懂。
正确的单例实例检查(附带线程安全建议)
你给出的两个示例都存在线程安全问题:当多个线程同时调用getInstance()/instance()时,可能会创建多个实例。结合现代C++的最佳实践,正确的实现可以这样写:
方式1:C++11及以后的线程安全懒汉式(推荐)
利用C++11标准规定的"局部静态变量初始化是线程安全的"特性,不需要手动加锁,还能避免内存泄漏:
class Singleton { private: Singleton() {} // 私有构造函数,禁止外部实例化 public: // 删除拷贝构造和赋值运算符,禁止拷贝 Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Singleton& getInstance() { static Singleton instance; // 局部静态变量,首次调用时初始化,线程安全 return instance; } };
这种写法里不需要手动检查实例是否存在,编译器会帮我们处理初始化的线程安全问题。
方式2:如果必须用指针实现(比如兼容旧代码)
如果一定要用指针形式,同时保证线程安全,需要加锁(比如用std::mutex),并且用nullptr明确检查:
#include <mutex> class Singleton { private: static Singleton* p_inst; static std::mutex inst_mutex; Singleton() {} public: Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Singleton* instance() { std::lock_guard<std::mutex> lock(inst_mutex); // 加锁保证线程安全 if (p_inst == nullptr) { // 用nullptr明确检查空指针 p_inst = new Singleton(); } return p_inst; } }; // 静态成员初始化 Singleton* Singleton::p_inst = nullptr; std::mutex Singleton::inst_mutex;
总结一下:
- 在现代C++(C++11+)里,优先用
nullptr代替0来表示空指针,语义更清晰。 !p_inst是合法的写法,但显式比较nullptr可读性更好。- 单例实现一定要注意线程安全,局部静态变量的方式是最简单且安全的现代写法。
内容的提问来源于stack exchange,提问作者Rahul Iyer
相关产品推荐
相关产品推荐

