单件模式

Singleton Pattern: 确保一个类只有一个实例,并提供一个全局访问点。

  1. 私有化它的构造函数,以防止外界创建单例类的对象;
  2. 使用类的私有静态指针变量指向类的唯一实例;
  3. 使用一个公有的静态方法获取该实例。

懒汉模式

1.1 版本: 单线程模式

  1. class Singleton
  2. {
  3. private:
  4. static Singleton* instance;
  5. private:
  6. Singleton() = default;;
  7. ~Singleton() = default;;
  8. private:
  9. class Deletor {
  10. public:
  11. ~Deletor() {
  12. if(Singleton::instance != nullptr)
  13. delete Singleton::instance;
  14. }
  15. };
  16. // 当程序结束时会调用静态成员deletor的析构函数来删除Singleton唯一实例
  17. static Deletor deletor;
  18. public:
  19. static Singleton* getInstance() {
  20. if(instance == nullptr) {
  21. instance = new Singleton();
  22. }
  23. return instance;
  24. }
  25. };
  26. // 静态成员变量必须在类外进行初始化
  27. Singleton* Singleton::instance = nullptr;

上述代码如果应用于多线程会出现竞争现象,需要用同步机制类保护共享数据,可以使用双重检测锁(DCL: Double-Checked Locking Pattern)

  1. static Singleton* getInstance() {
  2. if(instance == NULL) {
  3. Lock lock; // 基于作用域的加锁,超出作用域,自动调用析构函数解锁
  4. if(instance == NULL) {
  5. instance = new Singleton();
  6. }
  7. }
  8. return instance;
  9. }

线程安全问题仅出现在第一次初始化(new)过程中,而在后面获取该实例的时候并不会遇到,也就没有必要再使用lock。双检测锁很好地解决了这个问题,它通过加锁前检测是否已经初始化,避免了每次获取实例时都要首先获取锁资源。

C11规定了local static在多线程条件下的初始化行为,要求编译器保证了内部静态变量的线程安全性。在C11标准下,《Effective C++》提出了一种更优雅的单例模式实现,使用函数内的 local static 对象。这样,只有当第一次访问getInstance()方法时才创建实例。这种方法也被称为Meyers’ Singleton。C0x之后该实现是线程安全的,C0x之前仍需加锁。

1.2 版本

  1. // version 1.2
  2. class Singleton
  3. {
  4. private:
  5. Singleton() { };
  6. ~Singleton() { };
  7. Singleton(const Singleton&);
  8. Singleton& operator=(const Singleton&);
  9. public:
  10. static Singleton& getInstance()
  11. {
  12. static Singleton instance;
  13. return instance;
  14. }
  15. };

饿汉模式

指单例实例在程序运行时被立即执行初始化

  1. // version 1.3
  2. class Singleton
  3. {
  4. private:
  5. static Singleton instance;
  6. private:
  7. Singleton();
  8. ~Singleton();
  9. Singleton(const Singleton&);
  10. Singleton& operator=(const Singleton&);
  11. public:
  12. static Singleton& getInstance() {
  13. return instance;
  14. }
  15. }
  16. // initialize defaultly
  17. Singleton Singleton::instance;

由于在main函数之前初始化,所以没有线程安全的问题。但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。也即,static Singleton instance;和static Singleton& getInstance()二者的初始化顺序不确定,如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。

总结:

  • Eager Singleton 虽然是线程安全的,但存在潜在问题;

  • Lazy Singleton通常需要加锁来保证线程安全,但局部静态变量版本在C++11后是线程安全的;

  • 局部静态变量版本(Meyers Singleton)最优雅。