Appearance
单例模式
单例(Singleton)意图是:一个类型在程序中只有一个受控实例,并提供统一访问入口。它常被用于配置、日志器、注册表等;但它会引入全局状态、隐藏依赖和测试隔离困难,应谨慎使用,优先考虑依赖注入。
推荐:Meyers Singleton
C++11 起,函数内静态对象的初始化由语言保证线程安全,通常是最简洁可靠的懒加载实现:
cpp
class Logger {
public:
static Logger& instance() {
static Logger value;
return value;
}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
Logger(Logger&&) = delete;
Logger& operator=(Logger&&) = delete;
void info(std::string_view message);
private:
Logger() = default;
~Logger() = default;
};特点:
- 私有构造函数阻止外部任意构造;
- 静态访问函数提供唯一入口;
- 删除拷贝与移动操作,阻止复制出第二个实例;
- 首次调用时初始化,程序退出时自动析构;
- 首次初始化的线程安全由 C++11 标准保证,不需要手写双重检查锁。
饿汉式与懒汉式
- 饿汉式:非局部静态对象在程序启动阶段初始化,调用快但可能产生初始化顺序问题,并在不使用时也付出构造成本。
- 懒汉式:首次需要时才创建,节省不必要初始化;C++11 前常要自行同步,C++11 后优先使用函数内静态对象。
使用时的追问
- 析构顺序:多个全局/单例对象相互依赖时,程序退出阶段可能发生析构顺序问题;避免在静态对象析构中依赖其他静态对象。
- 测试:全局状态会污染测试,用接口 + 注入替代硬编码
instance()更易 mock。 - 多进程:单例只保证一个进程内实例;跨进程唯一性需文件锁、数据库约束、分布式锁或选主等机制。
- 生命周期:若资源必须明确启动/停止(线程池、网络服务),显式由应用组合根管理往往优于隐式单例。