0%

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

正确写法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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变量为空指针。从而导致实例化两次,从未导致硬件驱动加载两次,而导致崩溃。

1
2
3
4
5
6
7
object* object::get_instance() {
object* instance = nullptr;
if(instance == nullptr) {
instance = new object();
}
return instance;
}

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

1
2
3
4
5
6
7
8
9
10
11
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;
}