C++ 设计模式之单例模式

少于 1 分钟阅读

正确写法

该写法在第一次调用get_instance()后构造该实例,线程安全。

class object{
  public:
  ~object() = default;
  static std::unique_ptr<object>& get_instance();
  private:
  object() = default; ///< 构造函数写为private,防止其他调用者单独构造该对象实例。
};

std::unique_ptr<object>& object::get_instance();
  static std::unique_ptr<object> instance;  ///< 该对象的唯一实例
  static std::once_flag flag; ///< 标志位, 标记只调用一次
  std::call_once(flag, [&](){
    instance = std::make_unique<object>();  ///< C++14以后版本的方法
    /*
    instance = std::unique_ptr<object>(new object()); ///< C++14 到 C++11 可用的方法
    */
  });
  return instance;
}

其他写法比较

最简单的写法: 线程不安全 由于new object()这个构造的过程需要时间,所以可能造成两个线程同时获取到instance变量为空指针。从而导致实例化两次,从未导致硬件驱动加载两次,而导致崩溃。

object* object::get_instance() {
  object* instance = nullptr;
  if(instance == nullptr) {
    instance = new object();
  }
  return instance;
}

double check写法: 看起来线程安全,其实有条件竞争。 在#1#2处,可能发生一个线程正在对instance变量赋值(写操作), 而另一个线程在进行在进行判断instance变量是否为空(读操作),从而导致条件竞争,而导致崩溃。

object* object::get_instance(){
  static std::mutex mt;
  volatile object* instance = nullptr;  ///< volatile关键字为了防止编译器优化
  if(instance == nullptr) { ///< #1 读操作
    std::lock_guard<std::mutex> lock(mt);
    if(instance == nullptr) {
        instance = new object();  ///< #2 写操作
    }
  }
  return instance;
}