单例模式是一种常见的设计模式,用来保证一个类在整个程序运行期间只有一个实例。随着多线程编程的普及,线程安全的单例实现成为了一个热点话题。本文从几个常见的实现方式出发,分析它们的优缺点,并给出一种高效、延迟加载、可维护的实现方案。
1. 传统双检锁(Double‑Check Locking)
class Singleton {
public:
static Singleton& getInstance() {
if (instance_ == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_ == nullptr) {
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 mutex_;
};
- 优点:延迟加载,首次访问时才创建实例。
- 缺点:需要手动管理内存,容易导致内存泄漏;在 C++11 之前,
new的顺序和可见性问题导致双检锁不安全。 - 结论:仅在极端性能要求且对线程安全有特殊需求时才考虑使用。
2. 局部静态变量(Meyers Singleton)
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 线程安全的局部静态
return instance;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
- 优点:实现简洁,编译器负责线程安全初始化。C++11 标准保证局部静态初始化是线程安全的。
- 缺点:无法手动销毁实例,可能导致资源在程序退出前无法释放。
- 结论:在大多数场景下,这是最推荐的实现方式。
3. std::call_once + std::once_flag
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_;
};
- 优点:可在多线程环境下精确控制一次性初始化,且可以在程序结束时手动销毁。
- 缺点:实现略显冗长,仍需手动管理内存。
- 结论:适用于需要在程序运行期间显式销毁单例的场景。
4. C++17 的 std::shared_ptr + std::make_shared
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
static std::shared_ptr <Singleton> instance = std::make_shared<Singleton>();
return instance;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
- 优点:自动管理内存,允许多处引用共享单例。
- 缺点:如果某处忘记释放引用,可能导致单例不被销毁。
- 结论:适用于需要共享所有权的复杂系统。
5. 推荐实现方案(C++20+):std::once_flag + std::unique_ptr
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag_, []() {
instance_ = std::unique_ptr <Singleton>(new Singleton());
});
return *instance_;
}
// 为了可销毁,提供销毁函数
static void destroy() {
std::call_once(destroyFlag_, []() {
instance_.reset();
});
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::unique_ptr <Singleton> instance_;
static std::once_flag initFlag_;
static std::once_flag destroyFlag_;
};
- 特点:
std::call_once保证初始化仅执行一次,线程安全。std::unique_ptr自动释放资源,避免泄漏。- 可手动销毁,满足资源释放时机控制。
- 兼容 C++11 及以后版本。
6. 小结
- 最简单:Meyers Singleton(局部静态变量)。
- 需要手动销毁:
std::call_once+std::unique_ptr或std::shared_ptr。 - 高性能:双检锁在 C++11 之后已不再安全,除非对平台细节非常了解,否则不建议使用。
在实际项目中,优先考虑可读性、易维护性与线程安全。Meyers Singleton 由于其简洁与标准化,通常是首选方案。若有特殊需求,如在程序退出前必须释放资源,可结合 std::call_once 与智能指针进行改造。
祝你编码愉快!