Skip to content

单例模式

单例(Singleton)意图是:一个类型在程序中只有一个受控实例,并提供统一访问入口。它常被用于配置、日志器、注册表等;但它会引入全局状态、隐藏依赖和测试隔离困难,应谨慎使用,优先考虑依赖注入。

推荐:Meyers Singleton

C++11 起,函数内静态对象的初始化由语言保证线程安全,通常是最简洁可靠的懒加载实现:

cpp
class Logger {
public:
    static Logger& instance() {
        static Logger value;
        return value;
    }

    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    Logger(Logger&&) = delete;
    Logger& operator=(Logger&&) = delete;

    void info(std::string_view message);

private:
    Logger() = default;
    ~Logger() = default;
};

特点:

  • 私有构造函数阻止外部任意构造;
  • 静态访问函数提供唯一入口;
  • 删除拷贝与移动操作,阻止复制出第二个实例;
  • 首次调用时初始化,程序退出时自动析构;
  • 首次初始化的线程安全由 C++11 标准保证,不需要手写双重检查锁。

饿汉式与懒汉式

  • 饿汉式:非局部静态对象在程序启动阶段初始化,调用快但可能产生初始化顺序问题,并在不使用时也付出构造成本。
  • 懒汉式:首次需要时才创建,节省不必要初始化;C++11 前常要自行同步,C++11 后优先使用函数内静态对象。

使用时的追问

  1. 析构顺序:多个全局/单例对象相互依赖时,程序退出阶段可能发生析构顺序问题;避免在静态对象析构中依赖其他静态对象。
  2. 测试:全局状态会污染测试,用接口 + 注入替代硬编码 instance() 更易 mock。
  3. 多进程:单例只保证一个进程内实例;跨进程唯一性需文件锁、数据库约束、分布式锁或选主等机制。
  4. 生命周期:若资源必须明确启动/停止(线程池、网络服务),显式由应用组合根管理往往优于隐式单例。

使用 Markdown 与 VitePress 构建