在多线程环境下,单例模式需要保证实例化过程是原子且只执行一次。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++ 程序中安全、可靠地使用单例模式。