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

在多线程环境下,单例模式需要保证实例化过程是原子且只执行一次。C++11 之后,编译器对 static 局部变量的初始化进行了线程安全的保证,因此最简洁、最安全的实现方式是使用局部静态变量。下面分别介绍几种常见实现方式,并给出完整代码示例。


1. 局部静态变量(C++11 及以后)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;   // 线程安全的初始化
        return instance;
    }
private:
    Singleton()   = default;
    ~Singleton()  = default;
    Singleton(const Singleton&)            = delete;
    Singleton& operator=(const Singleton&) = delete;
};
  • 优点:实现最简洁,编译器保证初始化是线程安全的,无需显式锁。
  • 缺点:如果 Singleton 的构造函数抛异常,后续调用 instance() 会再次尝试构造,导致“重新初始化”问题,但在大多数场景下足以满足需求。

2. 静态局部对象 + std::call_once

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(initFlag_, [](){ instancePtr_ = new Singleton(); });
        return *instancePtr_;
    }
    static void destroy() {
        std::call_once(destroyFlag_, [](){ delete instancePtr_; });
    }
private:
    Singleton()   = default;
    ~Singleton()  = default;
    Singleton(const Singleton&)            = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instancePtr_;
    static std::once_flag initFlag_;
    static std::once_flag destroyFlag_;
};

Singleton* Singleton::instancePtr_ = nullptr;
std::once_flag Singleton::initFlag_;
std::once_flag Singleton::destroyFlag_;
  • 优点:显式控制实例的销毁,适合需要在程序退出时释放资源的情况。
  • 缺点:实现稍微繁琐,需手动维护指针和 once_flag

3. 原子指针 + 双检查锁(不推荐)

class Singleton {
public:
    static Singleton* instance() {
        Singleton* temp = instance_.load(std::memory_order_acquire);
        if (!temp) {
            std::lock_guard<std::mutex> lock(mutex_);
            temp = instance_.load(std::memory_order_relaxed);
            if (!temp) {
                temp = new Singleton();
                instance_.store(temp, std::memory_order_release);
            }
        }
        return temp;
    }
private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&)            = delete;
    Singleton& operator=(const Singleton&) = delete;

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

std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
  • 优点:适用于旧版 C++(< C++11)且想在构造失败时不抛异常。
  • 缺点:实现复杂,容易出现细微的同步错误,且在 C++11 以后已无必要。

4. Meyers Singleton 与析构顺序

使用局部静态对象时,析构顺序遵循 “最先构造,最先销毁” 规则,避免了 “静态销毁顺序问题”(Static Initialization Order Fiasco)。因此在单例中不需要显式手动销毁。


何时选择哪种实现?

需求 推荐实现
简洁、C++11+ 局部静态变量
需要手动销毁资源 std::call_once + 指针
支持 C++03 或无线程安全构造 原子指针 + 双检查锁
关注性能 局部静态变量(只在第一次调用时才会进行一次锁)

示例:使用单例实现一个全局配置管理器

#include <string>
#include <unordered_map>

class ConfigManager {
public:
    static ConfigManager& get() {
        static ConfigManager instance;
        return instance;
    }

    void set(const std::string& key, const std::string& value) {
        std::lock_guard<std::mutex> lock(mutex_);
        config_[key] = value;
    }

    std::string get(const std::string& key) const {
        std::lock_guard<std::mutex> lock(mutex_);
        auto it = config_.find(key);
        return it != config_.end() ? it->second : "";
    }

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

    mutable std::mutex mutex_;
    std::unordered_map<std::string, std::string> config_;
};
  • 线程安全的 set/get 操作通过 std::mutex 保护内部状态。
  • 单例实例保证了配置在整个程序生命周期中保持唯一。

小结

  • C++11 之后:推荐使用局部静态变量,最简单且已保证线程安全。
  • 需要显式销毁:可以结合 std::call_once 与原始指针。
  • 兼容旧标准:可使用原子指针 + 双检查锁,但实现更复杂。

只要按上述模式实现,即可在任何 C++ 程序中安全、可靠地使用单例模式。

发表评论