在 C++11 之后,标准库提供了多种支持线程安全的机制,使得实现线程安全的懒加载单例变得既简单又高效。本文从设计原则、实现方式以及性能优化三个方面,详细阐述如何在实际项目中正确使用单例模式。
1. 设计原则
- 单一实例:保证在整个程序生命周期中,单例类只产生一个对象。
- 延迟初始化:对象在第一次使用时才创建,避免无谓的资源占用。
- 线程安全:在多线程环境下,同一时刻只能创建一次实例。
- 易于使用:提供静态
getInstance()方法即可访问实例,使用者无需关注内部细节。
2. 实现方式
2.1 使用 std::call_once
C++11 引入的 std::call_once 与 std::once_flag 可以确保某段代码只执行一次,并且在多线程环境下是安全的。以下是最常见的实现方式:
#include <iostream>
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag_, [](){
instance_.reset(new Singleton);
});
return *instance_;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
void sayHello() const {
std::cout << "Hello from Singleton instance!\n";
}
private:
Singleton() { std::cout << "Singleton constructor\n"; }
~Singleton() { std::cout << "Singleton destructor\n"; }
static std::unique_ptr <Singleton> instance_;
static std::once_flag initFlag_;
};
std::unique_ptr <Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
优点:
- 代码简洁,易于维护。
std::call_once本身使用std::mutex保护,性能可靠。
2.2 静态局部变量(C++11 之后)
C++11 之后,局部静态变量的初始化是线程安全的。利用这一特性可以进一步简化实现:
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 线程安全初始化
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
void sayHello() const { std::cout << "Hello from Singleton!\n"; }
private:
Singleton() { std::cout << "Singleton constructed\n"; }
~Singleton() { std::cout << "Singleton destroyed\n"; }
};
优点:
- 代码最短,编译器自动保证线程安全。
- 延迟初始化,且只在第一次调用
getInstance()时创建。
注意:若你需要在销毁时执行一些资源释放,最好使用 std::unique_ptr 或者 std::shared_ptr,因为静态局部变量的销毁顺序可能导致依赖问题。
3. 性能优化
在高并发场景下,std::call_once 的锁实现可能会成为瓶颈。若你确定单例只在程序启动阶段创建,后续不再创建新实例,可以采用以下策略:
- 双重检查锁(Double-Checked Locking):适用于单例创建后不再销毁的情况。
class Singleton {
public:
static Singleton* getInstance() {
if (!instance_) { // 第一层检查
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) { // 第二层检查
instance_ = new Singleton();
}
}
return instance_;
}
// 其它成员...
private:
Singleton() {}
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
警告:在 C++11 之前,
Singleton* instance_需要使用std::atomic<Singleton*>或volatile来防止指令重排导致的未初始化访问。C++11 之后,使用std::atomic或者std::memory_order可以更安全。
- 使用
std::once_flag结合std::atomic_flag:更细粒度的控制。
4. 单例与 RAII 的结合
在现代 C++ 中,推荐使用 std::shared_ptr 或 std::unique_ptr 管理单例对象,并在内部使用 std::weak_ptr 避免循环引用:
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
static std::weak_ptr <Singleton> weakInstance;
std::shared_ptr <Singleton> sharedInstance = weakInstance.lock();
if (!sharedInstance) {
std::lock_guard<std::mutex> lock(mutex_);
sharedInstance = weakInstance.lock();
if (!sharedInstance) {
sharedInstance = std::shared_ptr <Singleton>(new Singleton);
weakInstance = sharedInstance;
}
}
return sharedInstance;
}
// ...
private:
Singleton() {}
static std::mutex mutex_;
};
std::mutex Singleton::mutex_;
这种实现方式可以在程序结束时自动释放单例资源,避免程序结束时资源泄露或析构顺序问题。
5. 小结
std::call_once与 静态局部变量 是实现线程安全懒加载单例最推荐的方法。- 对于极端高并发环境,可以采用双重检查锁或
std::atomic_flag进一步优化。 - 利用 RAII(
std::shared_ptr/std::unique_ptr)可自动管理资源,降低错误风险。
通过上述技巧,你可以在 C++ 项目中轻松实现既安全又高效的单例模式,为全局配置、日志系统、资源池等场景提供稳固的基础。