如何在 C++ 中实现线程安全的单例模式?

单例模式(Singleton)是一种常用的设计模式,用来保证一个类只有一个实例,并提供一个全局访问点。随着多线程程序的普及,传统的单例实现往往会出现并发安全问题。下面给出几种在 C++11 及以后版本中实现线程安全单例的方式,并讨论各自的优缺点。

1. 局部静态变量(Meyers 单例)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton inst;   // C++11 规定此处初始化是线程安全的
        return inst;
    }
    // 删除拷贝构造和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() { /* 资源初始化 */ }
    ~Singleton() { /* 资源释放 */ }
};

优点

  • 代码最简洁。
  • 只需一次初始化,且在第一次使用时才会创建实例,天然懒加载。
  • C++11 标准保证了线程安全的初始化。

缺点

  • 无法在程序退出时确定析构顺序,可能导致 “静态销毁顺序问题”。
  • 需要在函数内部定义静态对象,若想控制实例生命周期(如显式销毁)则不方便。

2. std::call_once + std::unique_ptr

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(initFlag, []{
            instancePtr.reset(new Singleton);
        });
        return *instancePtr;
    }

private:
    Singleton() {}
    ~Singleton() {}

    static std::unique_ptr <Singleton> instancePtr;
    static std::once_flag initFlag;
};

std::unique_ptr <Singleton> Singleton::instancePtr;
std::once_flag Singleton::initFlag;

优点

  • 通过 std::once_flag 明确表示只执行一次。
  • 使用 unique_ptr 可以更灵活地控制析构顺序(可在程序中手动销毁)。
  • Meyers 单例相比,避免了静态对象初始化时的潜在“静态销毁顺序问题”。

缺点

  • 代码稍微繁琐。
  • 仍需手动删除实例,可能导致内存泄漏风险。

3. 双重检查锁(Double-Check Locking)

class Singleton {
public:
    static Singleton* instance() {
        Singleton* tmp = instancePtr;
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instancePtr == nullptr) {
                instancePtr = new Singleton;
            }
            tmp = instancePtr;
        }
        return tmp;
    }

private:
    Singleton() {}
    ~Singleton() {}
    static Singleton* instancePtr;
    static std::mutex mtx;
};

Singleton* Singleton::instancePtr = nullptr;
std::mutex Singleton::mtx;

优点

  • 实现时不依赖 C++11 的线程安全静态变量,兼容老版本。
  • 只在第一次创建实例时锁,后续访问不需要锁,性能更好。

缺点

  • 实现错误率高,需要保证 instancePtr 采用 std::atomic<Singleton*> 或使用内存序列化。
  • 对于 C++11 及之后的标准,Meyers 单例已足够安全且更简洁,双重检查锁不再推荐。

4. 经典单例类(静态成员+构造函数私有化)

class Singleton {
public:
    static Singleton& getInstance() {
        return *instance;
    }
private:
    Singleton() {}
    ~Singleton() {}
    static Singleton* instance;
};

Singleton* Singleton::instance = new Singleton;

优点

  • 传统实现,易于理解。

缺点

  • 静态成员在程序结束前一直存在,无法控制销毁顺序。
  • 不是懒加载,实例在程序启动时即创建。
  • 线程不安全,需自行加锁。

小结

  • 推荐:使用 C++11 之后的局部静态变量实现(Meyers 单例)。其代码最简洁、性能最佳,并且标准已保证线程安全。
  • 需要显式销毁:可以结合 std::call_once + unique_ptr 的实现,手动管理生命周期。
  • 兼容旧标准:如果项目必须在 C++98/03 上编译,可使用双重检查锁(但务必使用 volatile/atomic 并保证内存序列化)。

掌握上述实现方式后,你可以根据项目需求选择最合适的单例实现,并在多线程环境下保持代码的安全性与可维护性。

发表评论