单例模式
定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ------《设计模式》GoF
稳定点:类只有一个实例,提供全局的访问点。---- 抽象
变化点:有多个类都是单例,能不能复用代码。----扩展:继承;组合。
需要有私有的构造和析构;禁掉拷贝构造、拷贝构造、移动构造、移动赋值;有静态成员函数;有静态私有成员变量,通过它在堆上分配内存;
版本一
cpp
// c++智能指针:unique_ptr可以解决堆上内存不释放的问题
class Singleton {
public:
static Singleton * GetInstance() {
if (_instance == nullptr) {
_instance = new Singleton();//此时堆上分配的内存不会被释放,导致内存泄漏
}
return _instance;
}
private:
Singleton(){}; //构造
~Singleton(){};
Singleton(const Singleton &) = delete; //拷⻉ 构造
Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) = delete;//移动拷贝构造
static Singleton * _instance; // 静态全局区分配内存,指针指向堆上的内存块
};
Singleton* Singleton::_instance = nullptr;//静态成 员需要初始化
版本二
为什么静态成员函数当中可以访问该类对象的私有成员?同一个类的对象都可以访问私有成员;
静态成员函数在 C++ 中与普通成员函数有所不同。它的一个关键特性是,静态成员函数不依赖于类的实例(即对象),而是属于类本身。因此,静态成员函数没有 this 指针,不能访问类的非静态成员(如实例的成员变量和成员函数)。但是,静态成员函数可以访问类的私有成员,这是因为静态成员函数依然是类的一部分,编译器会认为静态成员函数是该类的"一部分"。即使静态成员函数没有对象实例,类的私有成员(包括私有变量和私有成员函数)对类的静态成员函数是可见的。
解决了内存泄漏,可以调用析构函数进行delete
cpp
#include<cstdlib>
class Singleton {
public:
static Singleton * GetInstance() {
if (_instance == nullptr) {
_instance = new Singleton(); // 会产生资源竞争
atexit(Destructor);// 调用该函数,解决了内存泄露问题
}
return _instance;
}
private:
static void Destructor() {
if (nullptr != _instance) { // 此时主动释放内存,指针置空
delete _instance;
_instance = nullptr;
}
}
Singleton(){}; //构造
~Singleton(){};
Singleton(const Singleton &) = delete; //拷⻉构造
Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) = delete;//移动拷贝构造
static Singleton * _instance;
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
// 还可以使⽤内部类,智能指针来解决; 此时还有线程安全问题
版本三
加入了多线程,且引入了双重检测,主要用来解决读和写,因为只有第一次 访问才会调用new,第一个 if (_instance == nullptr)判断是专为读来准备的,只有第一次访问的时候才考虑加锁。
cpp
#include <mutex>
class Singleton { // 懒汉模式 lazy load
public:
static Singleton * GetInstance() {
// std::lock_guard lock(_mutex); // 3.1 切换线程 此时会浪费锁 :只有第一次访问全局检测点才涉及到写操作
if (_instance == nullptr) {
std::lock_guard lock(_mutex); // 3.2
if (_instance == nullptr) { // 双重检测double check
_instance = new Singleton(); //会产生资源竞争,所以加锁
// operator neww操作符
// 1. 分配内存
// 2. 调用构造函数
// 3. 返回指针
// 多线程环境下 cpu reorder操作
atexit(Destructor);
}
}
return _instance;
}
private:
static void Destructor() {
if (nullptr != _instance) {
delete _instance;
_instance = nullptr;
}
}
Singleton(){}; //构造
~Singleton(){};
Singleton(const Singleton &) = delete; //拷⻉构造
Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造
]Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) = delete;//移动拷贝构造
Singleton * _instance;
static std::mutex _mutex;
};
Singleton* Singleton::_instance = nullptr;//静态成 员需要初始化
std::mutex Singleton::_mutex; //互斥锁初始化
版本四
版本三是单线程,版本四是应对多核时代,考虑了指令重排。同步原语来解决,包括原子变量和内存栅栏
原子变量:原子执行的问题;可见性问题;执行序问题;
load:可以看见其他线程最新操作的数据
store:修改数据让其他线程可见
内存栅栏:可见性问题;执行序问题
cpp
// volitile
#include <mutex
#include <atomic>
class Singleton {
public:
/* static Singleton * GetInstance() {
// 原子变量解决 原子性 可见性 执行序
Singleton* tmp = _instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(_mutex);
tmp = _instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
tmp = new Singleton;
_instance.store(tmp, std::memory_order_release);
atexit(Destructor);
}
}
return tmp;
} */
//同步变量
// 原子变量解决 原子性 可见性
// 内存栅栏解决 执行序 可见性
static Singleton * GetInstance() {
Singleton* tmp = _instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acqui re);//获取内存屏障
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(_mutex);
tmp = _instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_relea se);//释放内存屏障
_instance.store(tmp, std::memory_order_relaxed);
atexit(Destructor);
}
}
return tmp;
}
private:
static void Destructor() {
Singleton* tmp = _instance.load(std::memory_order_relaxed);
if (nullptr != tmp) {
delete tmp;
}
}
Singleton(){}; //构造
~Singleton(){};
Singleton(const Singleton &) = delete; //拷⻉ 构造
Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) = delete;//移动拷贝构造
static std::atomic<Singleton*> _instance;
static std::mutex _mutex;
};
std::atomic<Singleton*> Singleton::_instance;//静 态成员需要初始化
std::mutex Singleton::_mutex; //互斥锁初始化
// g++ Singleton.cpp -o singleton -std=c++11
版本五
版本四的简化版
cpp
// c++11 magic static 特性:如果当变量在初始化的时候, 并发同时进⼊声明语句,并发线程将会阻塞等待初始化结束。
// c++ effective
class Singleton
{
public:
static Singleton& GetInstance() {
static Singleton instance;
return instance;
}
private:
Singleton(){}; //构造
~Singleton(){};
Singleton(const Singleton &) = delete; //拷⻉构造
Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) = delete;//移动拷贝构造
};
// 继承 Singleton
// g++ Singleton.cpp -o singleton -std=c++11
/*该版本具备 版本5 所有优点:
1. 利⽤静态局部变量特性,延迟加载;
2. 利⽤静态局部变量特性,系统⾃动回收内存,⾃动调⽤析构函数;
3. 静态局部变量初始化时,没有 new 操作带来的cpu指令 reorder操作;
4. c++11 静态局部变量初始化时,具备线程安全;
*/
版本六
在版本五基础上加了一个多态,解决了变化点问题
cpp
template<typename T>
class Singleton {
public:
static T& GetInstance() {
static T instance; // 这⾥要初始化 DesignPattern,需要调⽤DesignPattern 构造函数,同时会调⽤⽗类的构造函数。
return instance;
}
protected: // 让子类能够得以构造
virtual ~Singleton() {}
Singleton() {} // protected修饰构造函数,才能让别⼈继承
private:
Singleton(const Singleton &) = delete; //拷⻉ 构造
Singleton& operator=(const Singleton&) = delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) = delete;//移动拷贝构造
};
class DesignPattern : public Singleton {
friend class Singleton<DesignPattern>;
private:
DesignPattern() {}
~DesignPattern() {}
};
工厂方法
定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。 Factory Method使得一个类的实例化延迟到子类。 ------《设计 模式》GoF
为什么要有工厂模式而不直接使用new?除了new还有很复杂的构造流程。
解决了什么问题?
稳定点:创建同类对象的接口,对象创建接口;同类对象有一个相同的职责,功能接口。
变化点:创建对象的扩展
设计原则:最小知道原则;面向接口编程;
背景
实现一个导出数据的接口,让客户选择数据的导出方式;
要点
解决创建过程比较复杂,希望对外隐藏这些细节的场景; 比如连接池、线程池隐藏对象真实类型; 对象创建会有很多参数来决定如何创建; 创建对象有复杂的依赖关系;
本质
延迟到子类来选择实现;

抽象工厂
定义
提供一个接口,让该接口负责创建一系列"相关或者相互依赖的 对象",无需指定它们具体的类。 ------《设计模式》GoF
解决了什么问题?
稳定点:创建同类对象的接口,对象创建接口;同类对象有多个相同的职责,功能接口。
变化点:创建对象的扩展
工厂方法与抽象工厂的区别:抽象工厂需要创建一系列功能对象(多个功能接口);工厂方法创建一类功能的对象。
背景
实现一个拥有导出导入数据的接口,让客户选择数据的导出导 入方式;
结构图

责任链
定义
使多个对象都有机会处理请求,从而避免请求的发送者和接收 者之间的耦合关系。将这些对象连成一条链,并沿着这条链传 递请求,直到有一个对象处理它为止。 ------《设计模式》GoF
解决的问题:
稳定点:处理流程:请求按照链条传递:链表关系,接口;可打断
变化点:处理节点的个数;处理顺序;处理节点。
要点
-
解耦请求方和处理方,请求方不知道请求是如何被处理,处理方 的组成是由相互独立的子处理构成,子处理流程通过链表的方式 连接,子处理请求可以按任意顺序组合;
-
责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;
-
责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;
-
将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展,同时职责顺序也可以任意扩展;
本质
分离职责,动态组合;
结构图
