单例模式(Singleton Pattern)是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。

在C++中,单例模式常用于需要全局唯一实例的场景,例如日志系统、配置管理器或数据库连接池。下面详细介绍其实现方法和作用。


单例模式的实现方法

以下是几种常见的C++单例模式实现方式:

1. 懒汉式(Lazy Initialization)

延迟初始化,在第一次使用时创建实例。需要注意线程安全问题。

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

class Singleton {
public:
static Singleton* getInstance() {
if (instance == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mutex_);
if (instance == nullptr) { // 双重检查锁
instance = new Singleton();
}
}
return instance;
}

// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

void doSomething() {
std::cout << "Singleton instance: " << this << std::endl;
}

private:
Singleton() {} // 私有构造函数
static Singleton* instance;
static std::mutex mutex_;
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;

int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->doSomething();
s2->doSomething(); // 地址相同,说明是同一个实例
delete Singleton::instance;//要delete
return 0;
}

特点

  • 使用双重检查锁(Double-Checked Locking)确保线程安全。
  • 延迟加载,节省资源,但首次访问可能有性能开销。
  • 需要手动管理内存(delete instance),否则可能导致内存泄漏。

2. 饿汉式(Eager Initialization)

程序启动时立即创建实例,避免线程安全问题。

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

class Singleton {
public:
static Singleton& getInstance() {
return instance;
}

// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

void doSomething() {
std::cout << "Singleton instance: " << this << std::endl;
}

private:
Singleton() {} // 私有构造函数
static Singleton instance;
};

// 静态成员初始化
Singleton Singleton::instance;

int main() {
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
s1.doSomething();
s2.doSomething(); // 地址相同,说明是同一个实例
return 0;
}

特点

  • 程序启动时创建实例,线程安全。使用static使得运行时就创建,对比懒汉式使用静态的类的指针类型,他直接使用静态的类初始化
  • 占用资源较早,可能在未使用时就分配内存。
  • 实现简单,无需额外同步机制。

3. Meyers’ Singleton(C++11及以上推荐)

利用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
#include <iostream>

class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 局部静态变量,C++11保证线程安全
return instance;
}

// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

void doSomething() {
std::cout << "Singleton instance: " << this << std::endl;
}

private:
Singleton() {}
};

int main() {
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
s1.doSomething();
s2.doSomething(); // 地址相同,说明是同一个实例
return 0;
}

特点

  • 利用C++11静态局部变量的初始化是线程安全的特性。
  • 延迟加载,仅在首次调用getInstance时创建。
  • 自动管理内存(静态变量生命周期由程序控制)。
  • 实现简洁,推荐使用。

4. 使用智能指针

结合智能指针(如std::shared_ptr)管理单例实例的生命周期。

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

class Singleton {
public:
static std::shared_ptr<Singleton> getInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance) {
instance = std::make_shared<Singleton>();
}
return instance;
}

// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

void doSomething() {
std::cout << "Singleton instance: " << this << std::endl;
}

private:
Singleton() {}
static std::shared_ptr<Singleton> instance;
static std::mutex mutex_;
};

// 静态成员初始化
std::shared_ptr<Singleton> Singleton::instance = nullptr;
std::mutex Singleton::mutex_;

int main() {
auto s1 = Singleton::getInstance();
auto s2 = Singleton::getInstance();
s1->doSomething();
s2->doSomething(); // 地址相同,说明是同一个实例
return 0;
}

特点

  • 使用std::shared_ptr自动管理内存,避免手动释放。
  • 仍需线程同步机制(如std::mutex)来保护初始化。
  • 适合需要动态管理资源的场景。

单例模式的作用

  1. 全局唯一实例

    • 确保某个类只有一个实例,适合需要全局访问的资源,如配置管理器、日志记录器、线程池等。
  2. 资源共享

    • 避免重复创建开销大的对象(如数据库连接),提高资源利用率。
  3. 全局访问点

    • 提供统一的访问接口,方便在程序的任何地方使用同一实例。
  4. 控制资源访问

    • 通过单例控制对共享资源的访问(如文件系统或硬件设备),避免冲突。
  5. 延迟初始化(懒汉式)

    • 推迟实例创建到首次使用,优化启动性能。

注意事项

  1. 线程安全

    • 懒汉式需要显式加锁或使用C++11静态局部变量来保证线程安全。
    • 饿汉式和Meyers’ Singleton天生线程安全。
  2. 内存管理

    • 懒汉式需手动释放内存,或使用智能指针/静态变量自动管理。
  3. 单例的缺点

    • 可能导致全局状态,增加代码耦合性,难以测试。
    • 单例的滥用可能违反单一职责原则,需谨慎使用。
  4. 析构问题

    • 单例的销毁顺序可能引发问题,尤其在静态变量依赖其他全局对象时。

为什么局部静态变量能保证线程安全?

Meyers’ Singleton利用C++11及以上标准的局部静态变量特性,例如:

1
2
3
4
static Singleton& getInstance() {
static Singleton instance; // 局部静态变量
return instance;
}
  • 线程安全原因

    :C++11标准(ISO/IEC 14882:2011, §6.7)规定,局部静态变量的初始化是线程安全的。具体来说:

    • 编译器和运行时确保instance的初始化只发生一次,即使多个线程同时调用getInstance
    • 初始化过程由内部的同步机制(实现依赖,通常是编译器生成的锁)保护,避免竞争条件。
    • 初始化完成后,后续访问直接返回已初始化的对象,无需额外同步开销。
  • 优势:无需显式加锁,代码简洁,性能高效,且静态变量的生命周期由程序自动管理(程序结束时销毁)。


总结

  • 推荐实现:Meyers’ Singleton(C++11及以上),因其简洁、线程安全且自动管理内存。
  • 适用场景:需要全局唯一实例且访问频繁的场景,如日志、配置管理。
  • 注意事项:权衡单例的便利性和潜在问题(如耦合性、测试难度),避免滥用。

如果有更多具体问题或需要代码调试,请随时告知!