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

在多线程环境下,单例模式需要保证:

  1. 只创建一次实例;
  2. 对所有线程可见;
  3. 线程安全。

下面给出几种常见实现方式,并对比优缺点。

1. 经典懒汉式(双重检查锁定)

#include <mutex>

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

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

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

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

优点:延迟实例化,性能不错。
缺点:必须保证 new Singleton() 的构造函数不抛异常;在 C++11 之前,存在 static 变量初始化的重排序问题,需使用双重检查锁定。

2. Meyers 单例(函数内部静态局部变量)

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 标准保证线程安全。
缺点:实例化时间不确定,若需要在程序启动前创建,需要手动调用。

3. 采用 std::call_once

#include <mutex>

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(initFlag_, []() {
            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 initFlag_;
};

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

优点:显式控制一次性初始化,避免了重复检查。
缺点:代码相对繁琐,仍需手动删除实例(可放在 atexit 或使用 std::unique_ptr)。

4. 结合 std::shared_ptrstd::weak_ptr

如果需要在多个地方共享单例,可使用 std::shared_ptr

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        std::lock_guard<std::mutex> lock(mtx_);
        if (auto ptr = instance_.lock()) {
            return ptr;
        }
        auto ptrNew = std::shared_ptr <Singleton>(new Singleton());
        instance_ = ptrNew;
        return ptrNew;
    }

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::weak_ptr <Singleton> instance_;
    static std::mutex mtx_;
};

std::weak_ptr <Singleton> Singleton::instance_;
std::mutex Singleton::mtx_;

优点:可以让单例被安全地释放,避免内存泄漏。
缺点:略微增加开销,适用于生命周期需要动态管理的场景。

5. 小结

  • 最简洁:Meyers 单例(函数内部静态局部变量),推荐在 C++11 及以后使用。
  • 延迟初始化、可手动控制:双重检查锁定或 std::call_once
  • 支持共享与自动销毁std::shared_ptr/std::weak_ptr 方案。

在实际项目中,除非有特殊需求(如跨进程共享或自定义销毁时机),建议直接使用 Meyers 单例;它既简洁又安全。若对实例创建时间有严格控制,可考虑 std::call_once 或手动初始化。

发表评论