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

单例模式(Singleton Pattern)是软件设计中的一种常见模式,用于确保一个类只有一个实例,并提供一个全局访问点。在 C++ 中实现线程安全的单例模式需要考虑多线程环境下的初始化和访问问题。下面从多种实现方式进行讲解,并给出示例代码。


1. 经典 Meyers 单例(C++11 及以后)

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;   // C++11 保证线程安全的局部静态变量初始化
        return instance;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

优点:

  • 代码简洁、易读
  • 只在首次调用时初始化,后续调用几乎无开销
  • C++11 保证了局部静态变量的线程安全

缺点:

  • 需要 C++11 或更高标准
  • 不能在编译时对实例生命周期做更细粒度的控制(如延迟销毁)

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

#include <mutex>

class Singleton {
public:
    static Singleton* getInstance() {
        if (!instance_) {                     // 第一检查(无锁)
            std::lock_guard<std::mutex> lock(mutex_);
            if (!instance_) {                 // 第二检查(有锁)
                instance_ = new Singleton();
            }
        }
        return instance_;
    }
    // 记得实现析构时释放内存
    static void destroy() {
        std::lock_guard<std::mutex> lock(mutex_);
        delete instance_;
        instance_ = nullptr;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance_;
    static std::mutex mutex_;
};

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

优点:

  • 兼容 C++11 前的编译器
  • 只在第一次实例化时使用锁,后续访问高效

缺点:

  • 需要 volatile 或原子类型保证指针可见性(C++11 std::atomic 更推荐)
  • 若未正确实现,可能导致单例破坏

3. std::call_oncestd::once_flag

#include <mutex>

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(flag_, []() {
            instance_ = new Singleton();
        });
        return *instance_;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance_;
    static std::once_flag flag_;
};

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

优点:

  • 线程安全,且代码更简洁
  • std::once_flag 在多线程环境下只执行一次初始化,性能优秀

缺点:

  • 需要手动管理单例内存(如使用 std::unique_ptr 自动释放)

4. 采用 std::shared_ptr 自动销毁

#include <memory>
#include <mutex>

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        std::call_once(initFlag_, []() {
            instance_ = std::make_shared <Singleton>();
        });
        return instance_;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::shared_ptr <Singleton> instance_;
    static std::once_flag initFlag_;
};

std::shared_ptr <Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;

优点:

  • 自动管理生命周期,程序退出时销毁
  • 适合需要跨模块共享单例的场景

缺点:

  • 需要 C++11,且 shared_ptr 带来一定的开销

5. 结合 RAII 的单例

class Singleton {
public:
    static Singleton& instance() {
        static Singleton obj;   // 同 Meyers,但使用 RAII 管理资源
        return obj;
    }
private:
    Singleton() { /* 资源初始化 */ }
    ~Singleton() { /* 资源释放 */ }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

特点:

  • 通过 C++ 的 RAII(Resource Acquisition Is Initialization)实现资源安全管理
  • 对象在程序结束时自动析构,避免显式销毁

小结

  • C++11 及以后:推荐使用 Meyers 单例(局部静态变量)或 std::call_once + once_flag,两种方式都能确保线程安全,且实现简单。
  • C++11 前:可使用双重检查锁(DCL)或 std::mutex 手动实现,注意正确使用 volatilestd::atomic
  • 资源管理:如需在程序结束时释放资源,可结合 RAII 或 std::shared_ptr 自动销毁。

通过上述方案,你可以根据项目需求与编译环境选择最合适的实现方式,确保单例在多线程环境下安全、稳定地工作。

发表评论