正文:
在多线程环境下,单例模式(Singleton)需要保证实例只创建一次,并且在所有线程之间共享同一个实例。C++17 提供了多种工具可以帮助我们实现线程安全的单例,下面介绍两种常见且优雅的实现方式:std::call_once 与 constexpr 本地静态变量。
1. 使用 std::call_once 与 std::once_flag
#include <mutex>
#include <iostream>
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag, [](){ instance.reset(new Singleton()); });
return *instance;
}
void doWork() { std::cout << "Doing work\n"; }
private:
Singleton() { std::cout << "Singleton ctor\n"; }
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::unique_ptr <Singleton> instance;
static std::once_flag initFlag;
};
std::unique_ptr <Singleton> Singleton::instance;
std::once_flag Singleton::initFlag;
优点
- 只在第一次访问时创建实例,后续访问不再有同步开销。
std::call_once通过once_flag确保线程安全,且跨平台表现一致。
缺点
- 需要手动管理
std::unique_ptr,如果不小心会导致生命周期问题。
2. 使用局部静态变量(C++11 及以后)
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 之后已保证线程安全
return instance;
}
void doWork() { std::cout << "Doing work\n"; }
private:
Singleton() { std::cout << "Singleton ctor\n"; }
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
优点
- 代码简洁,编译器负责实例化和销毁。
- 自 C++11 起,局部静态变量的初始化是线程安全的。
缺点
- 在极少数情况下(如多进程共享同一可执行文件的内存映射),可能会导致多个实例。
- 对于具有显式销毁时机需求的场景,需自行实现。
3. 延迟销毁(懒销毁)与 std::shared_ptr
如果你希望在程序结束时自动销毁单例,或者允许多次调用 getInstance 后自动释放,可以结合 std::shared_ptr:
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
static std::shared_ptr <Singleton> instance(new Singleton(),
[](Singleton* p){ delete p; });
return instance;
}
// ...
private:
Singleton() {}
};
此实现让单例在最后一个引用销毁时自动释放,适用于需要显式资源管理的场景。
4. 小结
- 推荐:在大多数项目中,使用局部静态变量(C++11 以上)最简单、最安全;若需要显式控制实例生命周期,可改用
std::call_once+unique_ptr。 - 注意:单例类的构造函数应为私有,拷贝构造和赋值运算符禁止。若单例中持有线程、文件句柄等资源,确保在析构时正确释放。
- 性能:第一次实例化时会有一次原子操作或锁,后续访问几乎无同步开销。
通过以上两种方式,你可以在 C++17 环境下实现一个线程安全且易于维护的单例模式。祝你编码愉快!