在多线程环境下,单例模式需要保证只有一个实例,并且在并发访问时不产生竞争。C++11 以后,标准提供了原子操作、内存序列化以及线程安全的静态局部变量初始化等特性,使实现线程安全的单例变得更简单。下面给出几种常见实现方式,并对比其优缺点。
1. 基于局部静态变量的懒汉式
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11 规定此初始化是线程安全的
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {}
};
优点
- 代码简洁,几行即可实现。
- 只在第一次调用时进行初始化,后续访问几乎没有开销。
- 编译器保证线程安全,不需要显式加锁。
缺点
- 如果
Singleton的构造抛出异常,后续访问会再次尝试构造,可能导致多次构造失败。 - 无法控制实例销毁的时机(在程序结束时由系统回收)。
2. 双重检查锁(Double-Checked Locking)
class Singleton {
public:
static Singleton* getInstance() {
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() {}
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
优点
- 只在第一次实例化时加锁,后续访问不需要加锁,性能更好。
- 可以在需要时手动销毁实例(通过
delete)。
缺点
- 代码复杂,容易出现错误(例如忘记使用
memory_order)。 - 在旧编译器或未实现强内存模型的实现上可能不安全。
3. std::call_once 与 std::once_flag
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag_, []() {
instance_ = new Singleton();
});
return *instance_;
}
// 其他成员
private:
Singleton() {}
static Singleton* instance_;
static std::once_flag initFlag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
优点
- 代码可读性好,
call_once把锁的细节封装了。 - 保证单次初始化,线程安全。
缺点
- 仍然使用裸指针,需要手动销毁。
- 需要配合
std::unique_ptr或在atexit中销毁。
4. std::unique_ptr 与 std::shared_ptr
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
std::call_once(initFlag_, []() {
instance_.reset(new Singleton());
});
return instance_;
}
private:
Singleton() {}
static std::shared_ptr <Singleton> instance_;
static std::once_flag initFlag_;
};
std::shared_ptr <Singleton> Singleton::instance_;
std::once_flag Singleton::initFlag_;
优点
- 自动内存管理,避免手动
delete。 - 可通过
shared_ptr的引用计数实现延迟销毁。
缺点
- 引入额外的
shared_ptr运行时成本。 - 需要注意循环引用问题。
5. 模板化单例(对多类型共用同一实现)
template <typename T>
class Singleton {
public:
static T& instance() {
static T instance;
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
protected:
Singleton() {}
};
使用时:
class Foo : public Singleton <Foo> {
friend class Singleton <Foo>;
Foo() {}
public:
void doSomething() {}
};
优点
- 可以为不同类复用同一单例实现。
- 代码仍然保持简洁。
小结
- 最简单:局部静态变量(懒汉式)— 适用于大多数情况,C++11 及以后已保证线程安全。
- 需要手动销毁:
std::call_once+new/delete或unique_ptr/shared_ptr。 - 性能优化:双重检查锁(需小心实现)。
- 复用:模板化单例。
在实际项目中,首选局部静态变量实现;如果需要在特定时间销毁实例或需要跨平台支持更老的编译器,考虑使用 std::call_once 或双重检查锁。