现代c++单例模式
“Meyers Singleton” 在 C++11 之后最常用、最经典的实现写法,
1. 最小可用版(Meyers Singleton)
class Singleton {
public:
static Singleton& instance() // ① 按需创建,线程安全
{
static Singleton obj; // ② “魔法静态”——C++11 起只初始化一次
return obj;
}
// 对外接口示例
void doSomething() { /* ... */ }
private:
Singleton() = default; // ③ 禁止外部构造 / 析构 / 拷贝
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
关键点说明
- 局部静态变量
static Singleton obj
C++11 标准保证其初始化 按需且仅一次,并且线程安全(§6.7/4)。 - 拷贝 & 赋值删除
防止用户复制得到多个实例。 - 析构仍会在
main()
返回后自动调用
如果担心“静态析构顺序”问题,可换成第 3 节的“泄漏式”写法。
2. 模板泛化版
当你需要多个不同类都想“变成单例”时,可把样板逻辑抽成模板:
template <typename T>
class Singleton {
public:
static T& instance()
{
static T obj; // T 必须有可见的默认构造函数
return obj;
}
Singleton() = delete;
~Singleton() = delete;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 使用:
class Logger {
public:
void log(const std::string& msg) { /* ... */ }
};
int main() {
Singleton<Logger>::instance().log("hello");
}
3. “不析构”写法(工业界更常用)
若你的单例在程序退出阶段被别的静态对象再次访问,可以把实例放在堆上并 永不释放,由操作系统回收内存,彻底规避“静态析构顺序”风险:
class SingletonNoDtor {
public:
static SingletonNoDtor& instance()
{
static SingletonNoDtor* p = new SingletonNoDtor; // 只 new 一次
return *p;
}
void foo() { /* ... */ }
private:
SingletonNoDtor() = default;
~SingletonNoDtor() = default; // 仍然 private,但永远不会被调用
SingletonNoDtor(const SingletonNoDtor&) = delete;
SingletonNoDtor& operator=(const SingletonNoDtor&) = delete;
};
绝大多数大型项目(Chrome、LLVM、Abseil 等)都采用这种“泄漏式单例”,因为它简单且可靠。
4. std::call_once
版(显式控制)
如果团队有“禁止局部静态对象”或“需要显式销毁”的编码规范,可用 std::call_once
+ unique_ptr
:
#include <memory>
#include <mutex>
class SingletonCO {
public:
static SingletonCO& instance()
{
std::call_once(initFlag_, [] { ptr_.reset(new SingletonCO); });
return *ptr_;
}
static void shutdown() { ptr_.reset(); } // 主动析构
private:
SingletonCO() = default;
~SingletonCO() = default;
SingletonCO(const SingletonCO&) = delete;
SingletonCO& operator=(const SingletonCO&) = delete;
static std::once_flag initFlag_;
static std::unique_ptr<SingletonCO> ptr_;
};
std::once_flag SingletonCO::initFlag_;
std::unique_ptr<SingletonCO> SingletonCO::ptr_;
5. 何时选哪一种?
场景 | 推荐实现 | 理由 |
---|---|---|
99% 普通后端 / 桌面程序 | § 1 或 § 3 | Meyers 足够好;若担心静态析构顺序就用泄漏式 |
需要可控生命周期 | § 4 | 手动 shutdown() ,适合单元测试或多次 dlopen() 场景 |
追求可测试/可替换 | 不要单例 | 用依赖注入(DI)或工厂模式会更灵活 |
小结
- C++11 已保证 单例初始化 的线程安全,经典写法就是“局部静态 + 删除拷贝”。
- 析构阶段 才是单例真正的坑:如果你不确定别的静态对象会不会在析构里再次用到它,就“让它泄漏”最保险。
- 以上几种写法皆为工业界常用模式,可根据团队规范与场景自由选择