初始化可能失败的单例模式总结

初始化可能失败的单例模式(C++)总结

一、核心需求

在多线程环境下实现线程安全的单例模式,需满足以下关键要求:

  1. 单例对象仅初始化一次
  2. 支持处理初始化失败场景(返回nullptr
  3. 多线程安全(避免竞态条件)
  4. 基础约束:私有构造 / 析构函数、禁用拷贝 / 移动操作、全局唯一访问点(GetInstance()

二、两种实现方案

(一)双重检测锁实现(Singleton_V1)

1. 核心代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#pragma once
#include <atomic>
#include <mutex>
#include <stdexcept>
#include <iostream>

class Singleton_V1 {
public:
static Singleton_V1* GetInstance() {
Singleton_V1* inst = instance_.load(std::memory_order_acquire);
if (inst == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
inst = instance_.load(std::memory_order_relaxed);
if (inst == nullptr) {
try {
inst = new Singleton_V1();
instance_.store(inst, std::memory_order_release);
std::atexit(&Destroy);
} catch(const std::exception& e) {
// 记录失败日志
return nullptr;
}
}
}
return inst;
}

void dosomething() {
std::cout << "Singleton_V1 doing something." << std::endl;
}

private:
static inline std::atomic<Singleton_V1*> instance_ = {nullptr};
static inline std::mutex mutex_;

bool Init() {
// 耗时初始化操作,实际按需实现
return true;
}

Singleton_V1() {
if (!Init()) {
throw std::runtime_error("初始化失败");
}
}

static void Destroy() {
Singleton_V1 *inst = instance_.exchange(nullptr);
delete inst;
}

~Singleton_V1() {
std::cout << "Singleton_V1 destructed." << std::endl;
}

// 禁用拷贝和移动
Singleton_V1(const Singleton_V1 &) = delete;
Singleton_V1& operator=(const Singleton_V1&) = delete;
Singleton_V1(Singleton_V1&&) = delete;
Singleton_V1& operator=(Singleton_V1&&) = delete;
};

2. 设计要点

  • 双重检查锁机制:先通过原子指针快速检查,再通过互斥锁保证初始化唯一,兼顾性能与线程安全
  • 原子操作:使用std::atomic修饰实例指针,配合内存序(acquire/release)避免指令重排问题
  • 异常安全:
    • 内存分配失败直接抛出异常,catch后返回nullptr
    • 初始化失败时,构造函数抛出异常,内存自动释放,instance_未被赋值,下次可重试
  • 资源释放:通过std::atexit注册销毁函数,程序退出时自动释放单例对象

(二)静态局部变量实现(Singleton_V2)

1. 核心代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#pragma once
#include <stdexcept>
#include <iostream>

class Singleton_V2 {
public:
static Singleton_V2* GetInstance() {
try {
static Singleton_V2 instance;
return &instance;
} catch (const std::exception& e) {
// 记录失败日志
return nullptr;
}
}

void dosomething() {
std::cout << "Singleton_V2 doing something." << std::endl;
}

private:
bool Init() {
// 耗时初始化操作,实际按需实现
return true;
}

Singleton_V2() {
if (!Init()) {
throw std::runtime_error("初始化失败");
}
}

~Singleton_V2() {
std::cout << "Singleton_V2 destructed." << std::endl;
}

// 禁用拷贝和移动
Singleton_V2(const Singleton_V2 &) = delete;
Singleton_V2& operator=(const Singleton_V2&) = delete;
Singleton_V2(Singleton_V2&&) = delete;
Singleton_V2& operator=(Singleton_V2&&) = delete;
};

2. 设计要点

  • 线程安全:依赖 C++11 特性,静态局部变量的初始化是线程安全的(编译器保证)
  • 自动资源管理:利用 RAII 机制,程序退出时自动调用析构函数释放资源,无需手动管理
  • 异常处理:初始化失败时构造函数抛出异常,GetInstance()捕获后返回nullptr,下次调用仍会尝试初始化
  • 简洁高效:代码量少,无需手动维护互斥锁和原子变量,编译器优化充分

三、两种实现对比

特性 双重检测锁实现(Singleton_V1) 静态局部变量实现(Singleton_V2)
线程安全保障 原子操作 + 互斥锁手动实现 C++11 标准编译器自动保障
资源释放 std::atexit注册销毁函数 编译器自动调用析构函数
代码复杂度 较高(需处理原子 / 锁 / 内存序) 极低(依赖语言特性)
初始化重试支持 支持(失败后instance_nullptr 支持(失败后下次仍会尝试创建)
兼容性 支持 C++11 及以上(需 C++17 支持static inline 仅支持 C++11 及以上
性能 首次访问需加锁,后续无开销 编译器优化后性能接近,无锁开销