介绍一下C++中的设计模式,包括单例模式、工厂模式等。

单例模式

单例模式实现要点

  1. 构造函数和析构函数是私有的,不允许外部生成和释放
  2. 静态成员变量和静态返回单例的成员函数
  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
class Singleton1{
public:
static Singleton1 *GetInstance(){
if (_instance == nullptr){
_instance = new Singleton1();
}
return _instance;
}
private:
// 要点1:构造函数和析构函数是私有的,不允许外部生成和释放
Singleton1(){}
~Singleton1(){
std::cout << "~Singleton1()\n";
}
// 要点3:禁用拷贝构造和赋值运算符
Singleton1(const Singleton1&) = delete;
Singleton1& operator=(const Singleton1&) = delete;
Singleton1(Singleton1&) = delete;
Singleton1& operator=(Singleton1&) = delete;

private:
// 要点2:静态成员变量和静态返回单例的成员函数
static Singleton1 *_instance;
};
Singleton1* Singleton1::_instance = nullptr;

int main() {

Singleton1 *s1 = Singleton1::GetInstance();
return 0;
}
满足三要点,但为错误的单例实现,因为 Singleton1 不能得到正确的析构,无法调用 `~Singleton1(){}`,从而使得堆上的资源无法得到正确的释放。

示例二

那么为了解决 Singleton1 的问题,新增 atexit() 方法,使得在程序退出时,调用手动的析构函数,实现资源释放。

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
class Singleton2{
public:
static Singleton2 *GetInstance(){
if (_instance == nullptr){
_instance = new Singleton2();
atexit(Destructor); // 新增 atexit() 方法,使得在程序退出时,调用 Destructor 函数
}
return _instance;
}
private:
// 实现手动的析构
static void Destructor(){
if (nullptr == _instance){
delete _instance;
_instance = nullptr;
}
}

Singleton2(){}
~Singleton2(){
std::cout << "~Singleton2()\n";
}
Singleton2(const Singleton2&) = delete;
Singleton2& operator=(const Singleton2&) = delete;
Singleton2(Singleton2&) = delete;
Singleton2& operator=(Singleton2&) = delete;

private:
static Singleton2 *_instance;
};
Singleton2* Singleton2::_instance = nullptr;

int main() {

Singleton2 *s2 = Singleton2::GetInstance();
return 0;
}

多线程问题

示例三

那么为了实现一个线程安全的单例模式,可以引入互斥锁。

注意:
单例模式中的线程安全,并不是指这个对象是线程安全的,而是
创建和返回这个对象的过程是线程安全的。

单检测实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton3{
public:
static Singleton3 *GetInstance(){
std::lock_guard<std::mutex> lock(_mutex);
if (_instance == nullptr){
_instance = new Singleton3();
atexit(Destructor); // 新增 atexit() 方法,使得在程序退出时,调用 Destructor 函数
}
return _instance;
}
private:
... // 下同
private:
static Singleton3 *_instance;
};

但是这样加入互斥锁存在问题,即 正常情况下 _instance = new Singleton3(); 只会运行一次,大部分时候都是拿到 _instance 然后 return _instance;
所以,我们只需要在 _instance = new Singleton3(); 时才需要加互斥锁,而读取 _instance 的这种读操作没有必要加入互斥锁。

所以我们进行性能优化和代码改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton3{
public:
static Singleton3 *GetInstance(){
// std::lock_guard<std::mutex> lock(_mutex);
if (_instance == nullptr){
std::lock_guard<std::mutex> lock(_mutex); // 将互斥锁移到判断语句内
_instance = new Singleton3();
atexit(Destructor); // 新增 atexit() 方法,使得在程序退出时,调用 Destructor 函数
}
return _instance;
}
private:
... // 下同
private:
static Singleton3 *_instance;
};

但是这样存在一个问题,如果在多核的机器上,两个线程同时运行到 std::lock_guard<std::mutex> lock(_mutex); // 将互斥锁移到判断语句内 时,会出现 _instance 被 new 两次,故而我们在加锁后进行双检测。

优化完整的代码如下:

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
class Singleton3{
public:
static Singleton3 *GetInstance(){
if (_instance == nullptr){
std::lock_guard<std::mutex> lock(_mutex);
if (_instance == nullptr){ // 加入双检测
_instance = new Singleton3();
atexit(Destructor);
}
}
return _instance;
}
private:
static void Destructor(){
if (nullptr == _instance){
delete _instance;
_instance = nullptr;
}
}

Singleton3(){}
~Singleton3(){
std::cout << "~Singleton3()\n";
}
Singleton3(const Singleton3&) = delete;
Singleton3& operator=(const Singleton3&) = delete;
Singleton3(Singleton3&) = delete;
Singleton3& operator=(Singleton3&) = delete;

private:
static Singleton3 *_instance;
static std::mutex _mutex;
};
Singleton3* Singleton3::_instance = nullptr;
std::mutex Singleton3::_mutex

int main() {

Singleton3 *s3 = Singleton3::GetInstance();
return 0;
}

但是这种方式也是有问题的,即 没有考虑到 多线程的情况下 CPU指令重排的问题。
new操作符包含三部分:

  1. 分配内存
  2. 调用构造函数
  3. 返回对象指针

当发生指令重排,如出现1 -> 3 -> 2执行顺序,当进行 3.返回对象指针 时,_instance 不为 nullptr,若此时正好有另一个线程来访问 _instance,就直接 return,也就是这个线程拿到的是没有调用构造函数的对象指针,将出现内存泄漏问题。

示例四

那么为了解决 Singleton3 的问题,可以使用原子操作 和 内存屏障的方式

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
class Singleton4{
public:
static Singleton4 *GetInstance(){
Singleton4* tmp = _instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); // 加入内存屏障,acquire操作,即下面的代码不能被优化到上面

if (tmp == nullptr){
std::lock_guard<std::mutex> lock(_mutex);
tmp = _instance.load(std::memory_order_relaxed);

if (tmp == nullptr){
tmp = new Singleton4();
std::atomic_thread_fence(std::memory_order_release); // 释放内存屏障,release操作,即上面的代码不能被优化到下面
_instance.store(tmp, std::memory_order_relaxed); //内存可见性,store写操作,利用原子操作,使其他线程读取到的是最新值
atexit(Destructor);
}
}
return tmp;
}
private:
static void Destructor(){
Singleton4* tmp = _instance.load(std::memory_order_relaxed);
if (nullptr == tmp){
delete tmp;
tmp = nullptr;
}
}

Singleton4(){}
~Singleton4(){
std::cout << "~Singleton4()\n";
}
Singleton4(const Singleton4&) = delete;
Singleton4& operator=(const Singleton4&) = delete;
Singleton4(Singleton4&) = delete;
Singleton4& operator=(Singleton4&) = delete;

private:
static Singleton4 *_instance;
static std::mutex _mutex;
};
Singleton4* Singleton4::_instance = nullptr;
std::mutex Singleton4::_mutex

int main() {

Singleton4 *s4 = Singleton4::GetInstance();
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
class Singleton5{
public:
static Singleton5 *GetInstance(){
static Singleton5 instance; // 利用C++11静态局部变量
return &instance;
}
private:
Singleton5(){}
~Singleton5(){
std::cout << "~Singleton5()\n";
}
Singleton5(const Singleton5&) = delete;
Singleton5& operator=(const Singleton5&) = delete;
Singleton5(Singleton5&) = delete;
Singleton5& operator=(Singleton5&) = delete;
};

int main() {

Singleton5 *s5 = Singleton5::GetInstance();
return 0;
}

示例六

觉得还是不够优雅?
那还可以利用类模板,将单例的三要点进行 抽象封装

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
template<typename T> // 利用类模板
class Singleton6{
public:
static T *GetInstance(){
static T instance;
return &instance;
}
protected: // 用protected 而不是 private 目的是让子类能够调用构造和析构函数
Singleton6(){}
virtual ~Singleton6(){
std::cout << "~Singleton6()\n";
}
private:
Singleton6(const Singleton6&) = delete;
Singleton6& operator=(const Singleton6&) = delete;
Singleton6(Singleton6&) = delete;
Singleton6& operator=(Singleton6&) = delete;
};

class DesignPattern : public Singleton6<DesignPattern> {
friend class Singleton6<DesignPattern>;

private:
DesignPattern(){};
~DesignPattern(){
std::cout << "~DesignPattern()\n";
}
}

int main() {

DesignPattern *s6 = DesignPattern::GetInstance();
return 0;
}