C++设计模式之单例模式
_学习B站李建忠老师课程记录
动机:
1.在软件系统中,经常有这样一些特殊的类。必须保证他们在系统中只存在一个实例,才能确保他们的正确逻辑正确性、以及良好的效率。
2.如何绕过常规的的构造器,提供一种机制来保证一个类只有一个实例?
3.这应该是类设计者的责任,而不是使用者的责任。
模式定义:
保证一个类仅有一个实例,并且提供一个该实例的全局访问点。-GoF
代码:
1. 线程非安全的版本以及安全版本
class Singleton{
private://不设置则默认公有 自动设置
Singleton(){};//拷贝构造函数
Singleton(const Singleton& other){};//拷贝构造函数
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance=nullptr;
//线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//线程安全版本,但锁的代价过高 读也在等待
Singleton* Singleton::getInstance() {
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
2. 双检查锁版本
但由于内存读写reorder不安全
#include<bits/stdc++.h>
#include<algorithm>
#include<mutex>
using namespace std;
class Singleton {
private:
Singleton() {}
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton *m_instance;
static std::mutex m_mutex;
public:
static Singleton* getInstance() {
if (m_instance == nullptr) {
unique_lock<std::mutex> lock(m_mutex);
if (m_instance == nullptr) {
m_instance = new Singleton();
static Singleton_release cl;
}
}
return m_instance;
};
class Singleton_release
{
public:
~Singleton_release() //内嵌的清理类
//定义了释放singleton类的静态对象 当程序结束时候会触发Singleton_release的析构 完成释放
//Singleton的实例不能在自己本身的析构函数里写释放,因为实例是new出来的堆内存,必须手动delete
//1、单例中的 new 的对象需要delete释放。
//2、delete释放对象的时候才会调用对象的析构函数。
//3、如果在析构函数里调用delete,那么程序结束时,根本进不去析构函数,怎么会delete?
//4、如果程序结束能自动析构,那么就会造成一个析构的循坏,delete单例对象 调用析构函数 析构函数里面有delete对象 继续调用析构函数
if (Singleton::m_instance != nullptr) {
delete Singleton::m_instance;
Singleton::m_instance = nullptr;
}
}
};
void func() {
std::cout << "测试func" << endl;
};
};
//静态变量初始化
Singleton* Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutex;
int main() {
Singleton* p_test = Singleton::getInstance();
p_test->func();
system("pause");
return 0;
}
3. C++ 11版本之后的跨平台实现
#include<bits/stdc++.h>
#include<algorithm>
#include<mutex>
#include<atomic>
using namespace std;
class Singleton
{
private:
// static volatile Singleton * volatile local_instance;
static atomic<Singleton*> m_instance;
static std::mutex m_mutex;
Singleton() {
cout << "构造" << endl;
};
~Singleton() {
cout << "析构" << endl;
}
class rememberFree {
public:
rememberFree() {
cout << "成员构造" << endl;
}
~rememberFree() {
Singleton* local_instance = m_instance.load(std::memory_order_relaxed);
if (local_instance != nullptr) {
delete local_instance;
}
}
};
static rememberFree remember;//定义一个静态成员,在程序结束时系统会调用它的析构函数
public:
static Singleton* getInstance()
{
Singleton* tmp = m_instance.load(std::memory_order_relaxed);//定义了m_instace原子对象 屏蔽编译器的reorder
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence 获取内存栅栏
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
};
atomic<Singleton*> Singleton::m_instance;
Singleton::rememberFree Singleton::remember;
std::mutex Singleton::m_mutex;
int main()
{
cout << "Singleton第一次开始前" << endl;
Singleton* s = Singleton::getInstance();
cout << "Singleton访问第一次后" << endl;
cout << "Singleton访问第二次前" << endl;
Singleton* s2 = Singleton::getInstance();
cout << "Singleton访问第二次后" << endl;
return 0;
}
4. 推荐写法 Magic Static
#include <iostream>
using namespace std;
class Singleton
{
private:
Singleton() { std::cout << "Constructor!" << std::endl; };
Singleton(Singleton&) = delete;//禁用拷贝构造函数
Singleton& operator=(const Singleton&) = delete;//禁用重载赋值运算符
public:
~Singleton() { std::cout << "Destructor!" << std::endl; };
static Singleton& GetInstance()//返回引用
{
static Singleton ins;//静态局部变量,内存中只有一个,且只会被初始化一次
return ins;
}
void DoSomething();
};
void Singleton::DoSomething()
{
std::cout << "Singleton do something\n";
}
int main()
{
cout << "单例模式访问第一次前" << endl;
Singleton::GetInstance().DoSomething();
cout << "单例模式访问第一次后" << endl;
cout << "单例模式访问第二次前" << endl;
Singleton::GetInstance().DoSomething();
cout << "单例模式访问第二次后" << endl;
return 0;
}
要点总结
1.Singleton模式的实例构造器可以设置为protected以允许子类派生。
2.Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这样可能导致多个对象实例,与模式的初衷相违背。
3.如何实现多线程环境下安全的singleton?注意双检查锁的正确实现或者使用magic static写法。