4-4

设计模式之单例模式

设计模式

设计模式简单来说就是在解决某一类问题场景时,有既定的,优秀的代码框架可以直接使用,代码更易于维护,代码的可读性,复用性,可移植性,健壮性会更好

单例模式

概念

单例模式是指在内存中只会创建一个对象,且只能创建一个对象的设计模式,在程序中多次调用同一个对象且作用相同时,为了防止多次创建对象耗费大量内存,单例模式让程序在内存只创建一个对象,所有调用的地方共享该对象。

类型

懒汉式

在真正使用该对象时才去创建该对象,因为很懒~
懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象,否则则先执行实例化操作。
alt text

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
class Singleton 
{
public:
static Singleton* getInstance()
{
if(single == nullptr)
{
single = new Singleton();
}
return single;
}
private:
Singleton(){cout << "create" << endl;};
~Singleton()
{

cout << "delete" <<endl;
//delete single;

}
static Singleton* single;
// 定义一个嵌套类,在该类的析构函数中,自动释放外层类的资源
class CRelease
{
public:
~CRelease() { delete single; }
};
// 通过该静态对象在程序结束时自动析构的特点,来释放外层类的对象资源
static CRelease release;

};
Singleton* Singleton::single = nullptr;
Singleton::CRelease Singleton::release;

构造函数私有化,这样用户就不能任意定义该类型的对象了
定义该类型唯一的对象
通过一个static静态成员方法返回唯一的对象实例

由于析构函数被声明为私有的,无法通过delete 来释放掉内存, 为防止内存泄漏(虽然当前进程结束的时候,系统反正会回收分配给它的所有资源,包括未回收的内存),在类内嵌套了一个类,利用static静态对象在程序结束时自动析构这么一个特征。

懒汉式如何保证只创建一个对象

若多个线程同时判断 singleton == nullptr, 则会同时创建一个对象,如何保证安全?
通过加互斥锁 :

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
class Singleton 
{
public:
static Singleton* getInstance()
{
if(single == nullptr)
{
//获取锁
if(single == nullptr) //锁加双重判断,第一个if是保证了只有第一次创建对象时加锁解锁,保证了效率
//第二个if是保证了多个线程进入后不会建立多个对象
{

single = new Singleton();
}
//释放锁
}
return single;
}
private:
Singleton(){cout << "create" << endl;};
~Singleton()
{

cout << "delete" <<endl;
//delete single;

}
static Singleton* single;
// 定义一个嵌套类,在该类的析构函数中,自动释放外层类的资源
class CRelease
{
public:
~CRelease() { delete single; }
};
// 通过该静态对象在程序结束时自动析构的特点,来释放外层类的对象资源
static CRelease release;
static mutex mtx; //要声明为静态,静态成员函数只能访问静态数据成员和静态成员函数

};
Singleton* Singleton::single = nullptr;
Singleton::CRelease Singleton::release;

另一种保证线程安全且能在main结束后自动释放对象的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class CSingleton
{
public:
static CSingleton* getInstance()
{
static CSingleton single; // 懒汉式单例模式,定义唯一的对象实例,已经将拷贝构造和析构声明为private了,在类外无法拷贝赋值和调用析构,所以返回的是single的地址,而不是single本身
return &single;
}
private:
CSingleton() { cout << "CSingleton()" << endl; }
~CSingleton() { cout << "~CSingleton()" << endl;}
CSingleton(const CSingleton&);
};
int main()
{
CSingleton *p1 = CSingleton::getInstance();
CSingleton *p2 = CSingleton::getInstance();
CSingleton *p3 = CSingleton::getInstance();
return 0;
}

对于static静态局部变量的初始化,编译器会自动对它的初始化进行加锁和解锁控制,使静态局部变量的初始化成为线程安全的操作,不用担心多个线程都会初始化静态局部变量,因此上面的懒汉单例模式是线程安全的单例模式

静态局部变量的生命周期贯穿整个程序的运行时间,但是它们的初始化是延迟的,也就是说,它们只在首次被引用时初始化。这是C++标准中静态局部变量的一个重要特性,称为“Magic Statics”(也称作“函数内的静态局部变量”或“局部静态变量”)。

当你在函数内部声明一个静态局部变量时,这个变量的存储空间在程序开始运行时就已经被分配了,但是它的初始化会被推迟到该函数首次被调用,且到达该变量声明的时候。一旦初始化完成,该变量的值就会在程序的后续调用中保持不变,直到程序结束。

静态变量*******

饿汉式

在类加载时已经创建好了该单例对象,等待程序使用
在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。
alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton
{
public:
static Singleton* getInstance()
{
return &single;
}
private:
Singleton();
~Singleton();
static Singleton single;
}
Singleton Singleton::single;