1. 单例模式基础
禁用拷贝和移动共四个构造函数。
1.1 饿汉式(线程安全)
1. 代码实现
cpp
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
return instance;
}
};
// 在类外立即初始化
Singleton* Singleton::instance = new Singleton();
2. 实现原理
- 初始化时机 :在程序进入 main() 函数之前,由 C++ 运行时系统或动态链接器完成静态对象的初始化。
- 线程安全机制:依赖于操作系统的加载机制。在单线程的初始化阶段完成实例创建,因此天然线程安全,无需加锁。
3. 优点
- 实现简单:代码量少,逻辑直观。
- 天然线程安全:不存在多线程竞争问题。
- 访问速度快:getInstance 只是返回指针,无判断、无锁开销。
4. 缺点
- 资源浪费:无论程序是否使用该单例,它都会在启动时被创建。如果单例占用大量内存或包含耗时操作(如连接数据库),会拖慢启动速度。
- 静态初始化顺序问题 :如果该单例在构造函数中依赖了其他全局变量/静态对象 ,而那些对象尚未初始化,会导致未定义行为(Crash)。
- 场景:Singleton 依赖 GlobalConfig,但链接器可能先初始化 Singleton。
- 对象在堆上分配,不会自动析构。
- 无法控制析构顺序:程序退出时,静态对象的析构顺序是不确定的。如果其他静态对象在析构时使用了该单例,而单例已经析构,会导致 Segfault。
5. 适用场景
- 单例对象轻量,且肯定会被使用。
- 不依赖其他全局状态。
1.2 懒汉式(线程不安全)
1. 代码实现
cpp
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) { // 第一次检查
instance = new Singleton(); // 创建实例
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
2. 实现原理
- 初始化时机:第一次调用 getInstance 时。
- 线程安全机制 :无。完全依赖程序员的调用习惯。
3. 优点
- 延迟加载:只有真正使用时才创建,节省启动资源。
- 避免初始化顺序问题:因为是在函数内部调用,此时全局环境通常已就绪。
4. 缺点
- 线程不安全(严重) :
- 竞态条件:线程 A 和线程 B 同时调用 getInstance。
- A 检查 instance == nullptr (True)。
- B 检查 instance == nullptr (True)。
- A 创建对象,赋值给 instance。
- B 创建对象,赋值给 instance(覆盖 A 的指针,导致内存泄漏)。
- 或者两个线程同时 new,导致堆损坏。
- 性能隐患:即使加锁(见下文 DCL),每次调用都可能需要检查。
5. 适用场景
- 仅适用于单线程环境 。在 Linux 多线程服务器程序中严禁直接使用。
1.3 双重检查锁定 DCL(线程安全)
1. 代码实现
cpp
#include <atomic>
#include <mutex>
class Singleton {
private:
static std::atomic<Singleton*> instance;
static std::mutex mtx;
Singleton() {}
public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire); // 1. 第一次检查
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mtx); // 加锁
tmp = instance.load(std::memory_order_relaxed); // 2. 第二次检查
if (tmp == nullptr) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release); // 存储
}
}
return tmp;
}
};
std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;
2. 实现原理
- 核心思想:只在实例为空时加锁,实例创建后直接返回,减少锁竞争。
- 两次检查 :
- 第一次检查:避免不必要的加锁(性能优化)。
- 第二次检查:防止多个线程同时通过第一次检查后重复创建。
- 内存序 :
- 必须使用 std::atomic 配合 memory_order_acquire/release,防止指令重排。
3. 防止指令重排
new Singleton() 在底层汇编大致分为三步:
- malloc:分配内存。
- ctor:调用构造函数初始化内存。
- assign:将内存地址赋值给 instance 指针。
编译器/CPU 优化风险: 为了性能,步骤 2 和 3 可能重排为:1 -> 3 -> 2。
- 后果 :线程 A 执行了 1 和 3,此时 instance 非空,但对象尚未构造完成。线程 B 调用 getInstance,发现 instance 非空,直接返回并使用,导致访问未初始化的内存,程序 Crash。
- 解决:memory_order_release 确保存储操作前的所有写操作(构造)对其他线程可见;memory_order_acquire 确保加载操作后的读操作能看到之前的写操作。
4. 优点
- 线程安全:通过锁和原子操作保证。
- 延迟加载:按需创建。
- 高性能:实例创建后,后续调用无锁开销。
5. 缺点
- 对象在堆上分配,不会自动析构(和饿汉式一样)。
- 性能开销,每次调用都要做一次原子 load
1.4 Meyers(推荐)
1. 代码实现
cpp
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 魔法在这里
return instance;
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
2. 实现原理
- 标准保证 :静态局部变量的初始化是线程安全的。
- 底层机制 :
- 编译器会为 static Singleton instance 生成一个隐藏的守卫变量。
- 在 Linux (GCC/Clang) 下,通常通过调用 __cxa_guard_acquire 和 __cxa_guard_release 实现。
- 底层封装了 pthread_once 或原子锁机制,确保初始化代码块只执行一次。
- 生命周期 :
- 创建:第一次控制流经过声明处时。
- 销毁:程序退出(main 返回或 exit 调用)时,按反向顺序析构。
3. 优点
- 线程安全:由语言标准和编译器保证,无需手动加锁。
- 延迟加载:第一次使用时才初始化。
- 自动内存管理:栈对象或静态存储区对象,无需 delete,无内存泄漏。
- 避免静态初始化顺序问题:因为是函数内局部静态,只有在函数被调用时才初始化,此时全局环境已就绪。
4. 缺点
- 析构顺序风险:如果其他静态对象的析构函数依赖此单例,且此单例先析构,会 Crash。(这是所有全局/静态单例都存在的问题,不仅仅是 Meyers)。
- 首次调用开销:第一次调用时有锁开销(但通常可忽略)。
5. 适用场景
- 现代 C++ 开发的首选方案。
2. 支持初始化失败的单例模式
2.1 DCL
cpp
#pragma once
#include <atomic>
#include <mutex>
#include <iostream>
#include <exception>
#include <cstdlib> // std::atexit 注册程序退出时的清理函数
/**
* @brief 支持初始化失败的双重检查锁单例模式
*
* 核心特性:
* 1. 线程安全:使用 atomic + mutex + 内存序保证多线程环境下的正确性
* 2. 懒加载:首次调用 GetInstance() 时才创建实例
* 3. 高性能:双重检查避免每次获取实例都加锁
* 4. 初始化容错:Init() 失败时抛出异常,外部可感知创建失败
* 5. 自动清理:通过 std::atexit 注册析构,避免内存泄漏
*/
class Singleton {
public:
/**
* @brief 获取单例实例(线程安全,支持初始化失败)
* @return Singleton* 成功返回实例指针,失败返回 nullptr
*
* 实现:双重检查锁模式(Double-Checked Locking Pattern, DCLP)
*/
static Singleton* GetInstance() {
// ========== 第一次检查:无锁快速路径 ==========
// 使用 memory_order_acquire 语义:
// - 保证后续对实例成员的访问不会重排到本次读取之前
// - 与 store 时的 release 语义配对,形成 "happens-before" 关系
Singleton* inst = instance_.load(std::memory_order_acquire);
// 如果实例已存在,直接返回(99% 的情况走这个分支,性能极高)
if (inst == nullptr) {
// ========== 加锁进入慢速路径 ==========
// lock_guard 保证作用域结束时自动释放锁,异常安全
std::lock_guard<std::mutex> lock(mutex_);
// ========== 第二次检查:防止重复创建 ==========
// 此时已持锁,其他线程不可能同时创建实例
// 使用 relaxed 即可,因为锁已经保证了同步
inst = instance_.load(std::memory_order_relaxed);
if (inst == nullptr) {
try {
// ========== 创建实例 ==========
// new 操作分两步:1. 分配内存 2. 调用构造函数
// 构造函数中会调用 Init(),可能抛出异常
inst = new Singleton();
// ========== 发布实例(关键)==========
// 使用 memory_order_release 语义:
// - 保证 new Singleton() 的所有写操作(包括 Init() 的副作用)
// 在 store 之前完成,防止指令重排导致其他线程看到"半初始化"的对象
// - 与上方 load(acquire) 配对,实现线程间同步
instance_.store(inst, std::memory_order_release);
// ========== 注册自动清理 ==========
// atexit 保证程序正常退出时调用 Destroy() 释放资源
// 注意:只注册一次即可,重复注册标准库会去重或忽略
std::atexit(Destroy);
} catch (const std::exception& e) {
// ========== 初始化失败处理 ==========
// 1. 构造函数/Init() 抛出异常时,new 表达式会自动释放已分配内存
// (C++ 保证:构造函数异常时,operator delete 会被调用)
// 2. instance_ 仍为 nullptr,下次调用会重试创建
// 3. 实际项目中应记录日志:LOG_ERROR("Singleton init failed: {}", e.what());
return nullptr; // 向调用者传达"创建失败"
}
}
}
return inst;
}
/**
* @brief 单例的业务方法示例
*/
void dosomething() {
std::cout << "Singleton doing something." << std::endl;
}
private:
// ========== 静态成员变量(C++17 inline static 可直接在类内定义)==========
// 原子指针:存储单例实例
// - 用 atomic 避免多线程同时读写指针导致数据竞争
// - 指针类型 支持懒加载和延迟初始化
static inline std::atomic<Singleton*> instance_ {nullptr};
// 互斥锁:保护实例创建过程的互斥访问
// - 只在实例不存在且需要创建时加锁,锁竞争概率极低
static inline std::mutex mutex_;
/**
* @brief 模拟初始化逻辑(可能失败)
* @return bool true=成功,false=失败
*
* 实际场景示例:
* - 加载配置文件失败
* - 连接数据库/网络资源超时
* - 权限校验不通过
* - 依赖组件初始化异常
*/
bool Init() {
// 示例:模拟 10% 概率初始化失败
// if (rand() % 10 == 0) return false;
// 实际项目中应实现具体初始化逻辑
return true; // 默认成功
}
/**
* @brief 私有构造函数:防止外部直接创建实例
* @throw std::runtime_error 当 Init() 失败时抛出
*/
Singleton() {
// 在构造函数中执行初始化,失败则抛出异常
// 异常会传播到 GetInstance() 的 try-catch 块中被捕获
if (!Init()) {
// 抛出异常前,new 分配的内存会自动释放(C++ 语言保证)
throw std::runtime_error("Singleton initialization failed");
}
}
/**
* @brief 静态销毁函数:注册给 std::atexit
*
* 注意:
* 1. 使用 exchange(nullptr) 原子地将指针置空并获取旧值
* - 防止多个 atexit 处理器或手动调用时重复 delete
* - 避免悬空指针和 double-free 问题
* 2. delete 会先调用析构函数,再释放内存
*/
static void Destroy() {
Singleton* inst = instance_.exchange(nullptr, std::memory_order_acq_rel);
delete inst; // 如果 inst 为 nullptr,delete 是安全的(无操作)
}
/**
* @brief 析构函数:资源清理
*/
~Singleton() {
// 实际项目中应在此释放持有的资源:文件句柄、网络连接、线程池等
std::cout << "Singleton destructed." << std::endl;
}
// ========== 禁用拷贝语义(单例核心要求)==========
Singleton(const Singleton&) = delete; // 禁止拷贝构造
Singleton& operator=(const Singleton&) = delete; // 禁止拷贝赋值
};
2.2 Meyers
cpp
#pragma once
#include <iostream>
#include <exception>
#include <atomic>
/**
* @brief Meyer's Singleton 实现
*
* 核心原理:利用 C++11 局部静态变量的线程安全初始化特性
*
* 优势:
* 1. 代码极简,无需手动管理 atomic/mutex
* 2. 编译器保证:局部静态变量初始化是线程安全的(magic statics)
* 3. 异常安全:构造函数抛异常时,下次调用会重试初始化(C++11 标准保证)
* 4. 自动析构:程序退出时按反向顺序自动销毁,无需手动注册 atexit
*
* 注意:
* - 首次初始化失败后,后续调用会重试(符合"可恢复失败"场景)
* - 如果初始化是"永久失败"(如配置错误),需额外逻辑避免无限重试
*/
class Singleton {
public:
/**
* @brief 获取单例实例(线程安全,支持初始化失败重试)
* @return Singleton* 成功返回实例指针,失败返回 nullptr
*
* C++11 关键保证:
* - 如果 Instance() 的局部静态变量初始化抛出异常,
* 则下次调用 GetInstance() 时会重新尝试初始化
* - 同一时刻只有一个线程执行初始化,其他线程阻塞等待
*/
static Singleton* GetInstance() {
try {
// C++11 起,编译器保证:
// 1. 多线程环境下,初始化只执行一次(隐式使用 std::call_once)
// 2. 如果初始化抛异常,标记为"未完成",下次调用重试
// 3. 初始化完成后,后续调用直接返回,零开销
static Singleton instance; // ← 线程安全 + 懒加载 + 自动析构
return &instance; // 返回栈上对象的地址(实际在静态存储区)
} catch (const std::exception& e) {
// LOG_ERROR("Singleton initialization failed: {}", e.what());
return nullptr; // 向调用者传达"当前不可用"
}
}
/**
* @brief 单例的业务方法示例
*/
void dosomething() {
std::cout << "Singleton doing something." << std::endl;
}
// 可选:提供显式销毁接口(测试/热重载场景)
// 注意:一般不建议手动销毁,让程序退出时自动清理更安全
static void Destroy() {
// 无法直接销毁局部静态变量,但可通过标志位"逻辑销毁"
// 实际项目中通常不需要此方法
}
private:
/**
* @brief 模拟初始化逻辑(可能失败)
* @return bool true=成功,false=失败
*
* 实际场景:
* - 加载配置文件
* - 建立数据库连接
* - 初始化线程池/网络模块
*/
bool Init() {
// 示例:模拟初始化逻辑
// if (config_not_found) return false;
return true; // 默认成功
}
/**
* @brief 私有构造函数:防止外部创建
* @throw std::runtime_error 初始化失败时抛出
*
* 关键:构造函数抛异常时:
* - 局部静态变量 instance 的初始化标记为"未完成"
* - 下次调用 GetInstance() 时会重新进入构造函数
* - 已分配的资源由构造函数内部负责清理(RAII)
*/
Singleton() {
if (!Init()) {
// 抛出异常前,确保已分配的资源已释放(RAII 原则)
throw std::runtime_error("Singleton initialization failed");
}
std::cout << "Singleton constructed." << std::endl;
}
/**
* @brief 析构函数:自动资源清理
*
* 调用时机:
* - 程序正常退出时(main 返回或 std::exit)
* - 按"与构造顺序相反"的顺序销毁静态对象
*/
~Singleton() {
// 释放资源:关闭连接、停止线程、flush 日志等
std::cout << "Singleton destructed." << std::endl;
}
// ========== 禁用拷贝语义(单例核心要求)==========
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};