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

在多线程环境下,单例(Singleton)模式需要保证只有一个实例,并且在所有线程中都能安全访问。以下几种实现方式在 C++17 及以上标准下都能保证线程安全且具有良好的性能。

1. Meyer’s Singleton(局部静态对象)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance; // C++11 起保证线程安全的初始化
        return instance;
    }

    // 禁止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    void do_something() { /* ... */ }

private:
    Singleton() {}  // 私有构造函数
};

优点

  • 简单直观
  • C++11 起局部静态对象的初始化是线程安全的(即使多个线程同时调用 instance(),也只会初始化一次)。
  • 对象在程序结束时自动析构,无需手动管理。

缺点

  • 如果需要在程序结束前显式销毁单例,需额外实现。例如使用 std::unique_ptr 并配合 std::atexit

2. 双重检查锁(Double-Checked Locking)

class Singleton {
public:
    static Singleton* instance() {
        if (!ptr_) {                       // 第一次检查
            std::lock_guard<std::mutex> lock(mtx_);
            if (!ptr_) {                   // 第二次检查
                ptr_ = new Singleton();
            }
        }
        return ptr_;
    }

    ~Singleton() { delete ptr_; }

private:
    Singleton() {}
    static Singleton* ptr_;
    static std::mutex mtx_;
};

Singleton* Singleton::ptr_ = nullptr;
std::mutex Singleton::mtx_;

优点

  • 只在第一次创建实例时锁定,后续访问不再涉及锁。

缺点

  • 需要手动销毁单例(如在 atexit 里调用),否则可能导致内存泄漏。
  • 代码稍显繁琐,且如果没有使用 volatile 或 C++11 的内存模型,可能存在重排序导致的 UB。

3. std::call_oncestd::once_flag

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(flag_, [](){ ptr_ = new Singleton(); });
        return *ptr_;
    }

private:
    Singleton() {}
    static Singleton* ptr_;
    static std::once_flag flag_;
};

Singleton* Singleton::ptr_ = nullptr;
std::once_flag Singleton::flag_;

优点

  • std::call_once 保证只调用一次初始化回调,内部已实现线程安全。
  • 代码更简洁,且不需要手动锁。

缺点

  • 需要手动销毁单例(可在 atexit 注册 delete ptr_;)。

4. 静态智能指针(现代 C++ 推荐)

class Singleton {
public:
    static std::shared_ptr <Singleton> instance() {
        static std::shared_ptr <Singleton> ptr(new Singleton());
        return ptr;
    }

private:
    Singleton() {}
};

优点

  • 通过 std::shared_ptr 自动管理生命周期。
  • 线程安全且可在多处共享实例。

缺点

  • 每次访问返回 std::shared_ptr,可能产生不必要的引用计数开销。

小结

  • 对于绝大多数场景,Meyer’s Singleton(局部静态对象)是最简洁且线程安全的实现。
  • 如需显式控制实例创建时间或销毁顺序,可考虑 std::call_oncestd::once_flag
  • 双重检查锁虽然在某些语言/编译器中可行,但在 C++11 之后 std::call_once 更安全、更简洁。

技巧提示

  1. 禁止拷贝构造/赋值:单例不应被拷贝。
  2. 懒初始化:仅在首次使用时才创建实例,避免不必要的开销。
  3. 资源释放:若单例持有外部资源(文件句柄、网络连接等),请在程序退出前显式释放或使用 RAII 包装。

通过上述实现方法,您可以根据项目需求选择最适合的线程安全单例实现,既保证性能,又确保代码的可维护性。

发表评论