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

在多线程环境下,单例模式的实现必须确保只有一个实例被创建,同时不产生竞争条件。下面给出几种常用且线程安全的实现方式,并比较其优缺点。


1. C++11 std::call_once + std::once_flag

#include <mutex>

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(initFlag_, []() {
            instance_ = new Singleton();
        });
        return *instance_;
    }
    // 禁止拷贝和移动
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    ~Singleton() = default;

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

Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
  • 优点:实现简单,利用了 C++11 标准库的原子操作,保证初始化仅执行一次。
  • 缺点:需要手动管理单例对象的销毁,若不加注意可能导致内存泄漏。

2. 局部静态变量(Meyers Singleton)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance; // 函数内局部静态变量
        return instance;
    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    ~Singleton() = default;
};
  • 优点:编译器负责初始化和销毁,代码极为简洁。自 C++11 起,局部静态变量的初始化已保证线程安全。
  • 缺点:若需要在程序结束前手动销毁实例(例如为释放资源),需要额外实现。

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

#include <atomic>
#include <mutex>

class Singleton {
public:
    static Singleton* instance() {
        Singleton* tmp = instance_.load(std::memory_order_acquire);
        if (!tmp) {
            std::lock_guard<std::mutex> lock(mutex_);
            tmp = instance_.load(std::memory_order_relaxed);
            if (!tmp) {
                tmp = new Singleton();
                instance_.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }

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

private:
    Singleton() = default;
    ~Singleton() = default;

    static std::atomic<Singleton*> instance_;
    static std::mutex mutex_;
};

std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
  • 优点:只在首次创建时加锁,后续访问几乎无锁,性能更好。
  • 缺点:实现复杂,容易出错;若忘记使用 memory_order,在某些编译器上可能出现数据竞争。

4. 静态局部对象 + std::unique_ptr

如果想在销毁时控制顺序,可结合 std::unique_ptr

class Singleton {
public:
    static Singleton& instance() {
        static std::unique_ptr <Singleton> instance(new Singleton());
        return *instance;
    }
    // ...
};
  • 优点:确保单例在程序退出时被正确析构,且析构顺序与其他 static 对象一致。
  • 缺点:与前面方法相同,代码稍显冗长。

选择哪种实现?

方案 线程安全性 代码复杂度 对销毁的控制 适用场景
std::call_once 手动 需要手动销毁或延迟销毁
局部静态(Meyers) ✅ (C++11+) 自动 简洁,常见做法
双重检查锁 手动 性能极致需求
unique_ptr + 静态 自动 需要控制析构顺序

在大多数情况下,局部静态变量(Meyers Singleton) 已经足够满足需求,代码最简洁且符合标准。若对线程安全有更细致的需求或需要在销毁时做特殊处理,可考虑 std::call_onceunique_ptr 版本。若性能至上且能接受复杂实现,双重检查锁也是一个可选方案。

发表评论