问题历史:
单例模式的提出是为了限制只能创建一个特定类的实例,这在实现全局管理器时非常有用(例如,日志记录器、资源池、配置器)。
问题:
实现单例看似简单,但在 C++ 中会遇到困难:线程安全性、销毁的正确性、初始化的顺序。
解决方案:
实现单例的最安全和现代的方法是在静态函数内部使用静态局部变量,这保证了在第一次调用时进行初始化,线程安全性(从 C++11 开始)和正确的销毁。
代码示例:
class Singleton { public: static Singleton& instance() { static Singleton s; return s; } void doSomething() {} private: Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };
关键特性:
是否可以通过序列化或克隆来创建第二个单例实例?
是的。如果实现序列化/反序列化方法或手动实现 clone(),而不限制复制构造函数,可能会出现第二个实例。为避免这个问题,需要禁止所有复制、克隆和通过序列化恢复的方式。
在 C++98/03 标准下通过局部静态变量实现的单例在多线程环境中是否正确?
否。在 C++11 之前,局部静态变量在初始化时不保证线程安全。这可能导致当两个线程同时进入 instance() 函数时创建多个实例。在 C++11 及更新版本中—这个问题在标准层面上得到了解决。
通过静态局部变量创建的单例实例何时被销毁?
对象在程序结束时(exit)按创建的相反顺序(LIFO)销毁。但如果在析构函数中访问已经销毁的对象,可能会导致问题。
在日志系统中,开发人员使用全局指针和 new 实现单例,忘记禁止复制。程序运行正常,但在多线程环境中,日志记录器的不同实例有时会出现,消息丢失。
优点:
缺点:
单例通过静态局部变量在静态函数中实现,禁止了复制。线程安全得到了保证,程序可以扩展,日志正确分隔。
优点:
缺点: