在多线程环境下,单例模式需要保证只有一个实例被创建,并且该实例在所有线程间共享。下面介绍几种常见的实现方式,并比较它们的优缺点。
1. 局部静态变量(C++11及以后)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // 编译器保证线程安全
return instance;
}
// 其他成员函数
private:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
优点
- 代码最简洁,直接利用语言特性。
- 编译器负责线程同步,无需手动写锁。
- 延迟初始化,第一次调用时才创建。
缺点
- 对于C++11之前的编译器不适用。
- 可能会出现“static initialization order fiasco”问题,虽然在函数内部局部静态已解决,但全局静态依旧需要注意。
2. 带锁的双重检查锁(DCL)
class Singleton {
public:
static Singleton* instance() {
if (!instance_) {
std::lock_guard<std::mutex> lock(mtx_);
if (!instance_) {
instance_ = new Singleton();
}
}
return instance_;
}
private:
Singleton() = default;
static Singleton* instance_;
static std::mutex mtx_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mtx_;
优点
- 兼容老版本C++。
- 只在第一次初始化时加锁,后续访问速度较快。
缺点
- 需要注意内存可见性问题(在C++11前未保证)。
- 实现相对复杂,易出错。
3. 静态局部指针与 std::call_once
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, []() {
instance_ = new Singleton();
});
return *instance_;
}
private:
Singleton() = default;
static Singleton* instance_;
static std::once_flag flag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
优点
- 兼容C++11及以后。
std::call_once语义清晰,线程安全。- 延迟初始化与双重检查类似。
缺点
- 需要手动管理内存释放(可使用智能指针改进)。
4. Meyer’s 单例(函数内部静态对象)+ 智能指针
class Singleton {
public:
static std::shared_ptr <Singleton> instance() {
static std::shared_ptr <Singleton> ptr(new Singleton());
return ptr;
}
private:
Singleton() = default;
};
优点
- 自动内存管理,避免手动
delete。 - 与 C++11 的线程安全局部静态兼容。
缺点
- 共享计数开销,若单例不需要多次获取,略显冗余。
5. 经典静态成员实现(全局变量)
class Singleton {
public:
static Singleton& getInstance() {
return *instance_;
}
private:
Singleton() = default;
static Singleton* instance_;
};
Singleton* Singleton::instance_ = new Singleton();
优点
- 简单实现,直接返回引用。
缺点
- 全局初始化顺序不确定,可能导致“静态初始化顺序问题”。
- 无法在需要时才初始化。
选择建议
- C++11 及以后:推荐使用局部静态变量或
std::call_once,代码最简洁且线程安全。 - C++03:若需兼容旧编译器,使用双重检查锁(DCL)或
std::call_once的老实现。 - 性能极端要求:如果后续访问频繁且不想再加锁,
std::call_once仍是最优。 - 需要自动析构:可结合
std::shared_ptr或std::unique_ptr,避免手动释放。
小结
线程安全单例是 C++ 设计模式中的常见难题。理解每种实现的内部机制,可以帮助我们在不同的项目需求与编译器环境下做出合适选择。通过利用现代 C++ 的特性(如线程安全的局部静态、std::call_once、智能指针),可以大幅降低代码复杂度与错误率,从而编写出既简洁又可靠的单例类。