C++单例模式

1. 背景

由于业务算法的不断迭代,处理流程变的更复杂,以及面对内存压力,原来的C语言会让代码更臃肿。代码框架在逐步演进和重构,业务逻辑已经开始逐步采用现代C++进行重写,减少内存碎片和内存泄漏,用上了更高级的语法,吃上了细糠。有的代码已经存在了十几年,大家在上面做各种堆砌,corner cases的覆盖。不过现在好的一点是,可以借助大模型帮我们梳理代码结构,添加代码注释,帮我们画代码调用的流程图,方便我们更快的理解,提高代码阅读效率。以前刚入职那会儿可是要要花较多的时间一个个文件看一个个自己梳理。

自己在代码重构中用到了单例模式,用于存储某个独立的流程产生的数据,接下来的几个独立流程依赖这些数据进行计算,所以采用了单例模式作为数据仓储,用于其他几个独立流程能去访问其中的数据。

2. 什么是单例?

C++单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。 - 私有构造函数,防止外部new - 禁止拷贝/赋值(C++11: 删除 copy/move 构造/赋值) - 提供静态方法获取实例

3. 线程安全版本

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
#include <mutex>

class ThreadSafeSingleton
{
public:
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton(ThreadSafeSingleton&&) = delete;
ThreadSafeSingleton& operator=(ThreadSafeSingleton&&) = delete;

static ThreadSafeSingleton* getInstance()
{
//双重检查锁定
if (instance == nullptr)
{
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr)
{
instance = new ThreadSafeSingleton();
}
}
return instance;
}

priviate:
static ThreadSafeSingleton* instance;
static std::mutex mtx;

ThreadSafeSingleton() {}
~ThreadSafeSingleton() {}
}


4. C++11之后最优雅的实现(Meyer’s Singleton)

对于现代C++,推荐使用Meyer’s Singleton,因为:

  • 线程安全(C++11保证局部静态变量初始化是线程安全的)
  • 自动析构
  • 代码简洁
  • 延迟初始化(可以在程序启动时初始化)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MeyerSingleton {
public:
MeyerSingleton(const MeyerSingleton&) = delete;
MeyerSingleton& operator=(const MeyerSingleton&) = delete;
MeyerSingleton(MeyerSingleton&&) = delete;
MeyerSingleton& operator=(MeyerSingleton&&) = delete;

static MeyerSingleton& getInstance() {
static MeyerSingleton instance;
return instance;
}

void doSomething() {
//业务逻辑
}

private:
MeyerSingleton() {}
MeyerSingleton() {}
}

5. 单例模式的缺点

  • 单元测试困难(难以替换/注入 mock)
  • 全局状态,可能导致代码藕合度高
  • 隐式的全局状态,难以跟踪
  • 违背依赖注入原则,更推荐通过构造函数注入需要的资源