代码仓库shanchuann/CPP-Learninng

单例模式是保证一个类仅有一个实例,并提供全局访问点的设计模式,广泛应用于日志系统、配置管理、数据库连接池等需要全局唯一实例的场景。C++ 实现单例模式的核心是私有化构造函数、禁用拷贝构造与赋值运算符,根据实例创建时机分为懒汉模式和饿汉模式,二者在线程安全性、性能和资源占用上存在明显差异,本文结合完整可运行代码,详解四种经典实现方式。

饿汉模式

饿汉模式的核心是预初始化,即程序启动时(main函数执行前)就创建单例实例,无需延迟初始化,天然具备线程安全性,无需额外的同步机制。

静态成员变量饿汉模式(线程安全)

将单例实例定义为类的静态成员变量,在类外部完成初始化,程序加载阶段就创建实例,调用getInstance时直接返回实例引用,无空值判断和锁开销。

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
class Singleton {
private:
// 私有构造函数,禁止外部创建对象
Singleton(int val = 0):value(val) {
cout << "Singleton 构造函数被调用" << endl;
}
// 禁止拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
~Singleton() {
cout << "Singleton 析构函数被调用" << endl;
}
static Singleton s; // 静态成员变量,类加载时创建实例
int value; // 示例成员变量
public:
static Singleton& getInstance() {
return s; // 返回静态实例的引用
}
void show() {
cout << "Singleton: " << value << endl;
}
};

// 静态成员变量定义
Singleton Singleton::s(10); // 在类外定义静态成员变量
1
2
3
4
5
6
7
int main() {
Singleton& instance1 = Singleton::getInstance();
instance1.show();
Singleton& instance2 = Singleton::getInstance();
instance2.show();
return 0;
}

该实现的缺点是程序启动时就占用内存,即使实例从未被使用,适合实例使用频率高、对启动性能无要求的场景。

静态局部变量饿汉模式(C++11前线程不安全)

在 C++11 之前,静态局部变量的初始化不是线程安全的,这也是早期需要双检锁等复杂实现的原因。但 C++11 标准彻底解决了这个问题。当多个线程首次同时执行到 static Singleton s(10); 这一行时,只有一个线程会执行初始化逻辑,其他线程会被阻塞,直到该静态变量完全初始化完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Singleton {
private:
// 私有构造函数,禁止外部创建对象
Singleton(int val = 0) :value(val) {
cout << "Singleton 构造函数被调用" << endl;
}
// 禁止拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
~Singleton() {
cout << "Singleton 析构函数被调用" << endl;
}

int value; // 示例成员变量
public:
static Singleton& getInstance() {
static Singleton s(10); // 局部静态变量,第一次调用时创建实例
return s; // 返回静态实例的引用
}
void show() {
cout << "Singleton: " << value << endl;
}
};

“If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.”(如果控制流在变量初始化期间并发进入声明,并发执行将等待初始化完成。)

1
2
3
4
5
6
7
int main() {
Singleton& instance1 = Singleton::getInstance();
instance1.show();
Singleton& instance2 = Singleton::getInstance();
instance2.show();
return 0;
}

该实现结合了懒汉模式的延迟初始化和饿汉模式的线程安全,代码极简,无需手动管理互斥锁和原子操作,是 C++11 及以上版本的首选单例实现方式。

懒汉模式

懒汉模式的核心是延迟初始化,即程序运行过程中首次使用实例时才创建对象,避免程序启动时的资源占用,是开发中常用的单例实现方式。

基础懒汉模式(线程不安全)

基础懒汉模式是最简单的实现,无任何线程同步机制,仅适用于单线程环境。多线程并发调用时,多个线程可能同时通过空值判断,导致创建多个实例,违背单例原则。

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
class Singleton {
private:
// 私有构造函数,禁止外部创建对象
Singleton(int val = 0) :value(val) {
cout << "Singleton 构造函数被调用" << endl;
}
// 禁止拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
~Singleton() {
cout << "Singleton 析构函数被调用" << endl;
}
static Singleton* ps; // 静态成员指针,初始值为 nullptr
int value; // 示例成员变量
public:
static Singleton* getInstance() {
if (ps == nullptr) { // 第一次调用时创建实例
ps = new Singleton(10); // 创建实例并初始化成员变量
}
return ps; // 返回静态实例的引用
}
void show() {
cout << "Singleton: " << value << endl;
}
};
// 静态成员变量定义
Singleton* Singleton::ps = nullptr; // 初始化静态成员指针
void funa() {
Singleton* instance = Singleton::getInstance();
instance->show();
}
void funb() {
Singleton* instance = Singleton::getInstance();
instance->show();
}
1
2
3
4
5
int main() {
funa();
funb();
return 0;
}

该实现通过私有构造函数阻断外部实例化,用delete关键字禁用拷贝与赋值,静态指针ps初始化为空,首次调用getInstance时完成实例创建,后续调用直接返回已创建的指针。

双检锁懒汉模式(线程安全)

为解决基础懒汉模式的线程安全问题,采用双检锁(Double-Checked Locking)结合原子操作实现,既保证线程安全,又避免了每次获取实例都加锁的性能开销。C++ 中指令重排会导致双检锁失效,因此需要通过std::atomic和指定内存序保证实例创建的完整性。

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
class Singleton {
private:
// 私有构造函数,禁止外部创建对象
Singleton(int val = 0) :value(val) {
cout << "Singleton 构造函数被调用" << endl;
}
// 禁止拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
~Singleton() {
cout << "Singleton 析构函数被调用" << endl;
}
static std::atomic<Singleton*> ps; // 静态成员指针,初始值为 nullptr (atomic for DCL)
static std::mutex mtx; // 保护实例创建的互斥量
int value; // 示例成员变量
public:
static Singleton* getInstance() {
// 双检锁 (Double-Checked Locking)
Singleton* tmp = ps.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lk(mtx);
tmp = ps.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton(10); // 创建实例
ps.store(tmp, std::memory_order_release);
}
}
return tmp; // 返回静态实例指针
}
void show() {
cout << "Singleton: " << value << endl;
}
};
// 静态成员变量定义
std::atomic<Singleton*> Singleton::ps{ nullptr }; // 初始化静态成员指针
std::mutex Singleton::mtx; // 初始化互斥量
void funa() {
Singleton* instance = Singleton::getInstance();
instance->show();
}
void funb() {
Singleton* instance = Singleton::getInstance();
instance->show();
}

实现中std::mutex保证临界区的互斥访问,两次空值判断大幅减少锁竞争,std::atomic配合memory_order_acquirememory_order_release内存序,防止编译器和CPU的指令重排,确保实例创建完成后才对其他线程可见。

单例模式的析构函数私有化设计,保证实例无法被外部手动销毁,静态变量的生命周期与程序完全一致,程序退出时会自动调用析构函数释放资源。禁用拷贝构造和赋值运算符是单例模式的核心约束,从语法层面杜绝通过拷贝创建新实例的可能,保证实例的全局唯一性。