现代c++单例模式

cpp May 13, 2025

“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;
};

关键点说明

  1. 局部静态变量 static Singleton obj
    C++11 标准保证其初始化 按需且仅一次,并且线程安全(§6.7/4)。
  2. 拷贝 & 赋值删除
    防止用户复制得到多个实例。
  3. 析构仍会在 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 已保证 单例初始化 的线程安全,经典写法就是“局部静态 + 删除拷贝”。
  • 析构阶段 才是单例真正的坑:如果你不确定别的静态对象会不会在析构里再次用到它,就“让它泄漏”最保险。
  • 以上几种写法皆为工业界常用模式,可根据团队规范与场景自由选择

zzx

a programmer. github: https://github.com/zzxT