单例模式 Singleton Pattern
定义
保证一个类仅有一个实例, 并提供一个该实例的全局访问点
这个其实很好理解就是字面意思哈哈哈, 总算有个好理解的了
版本1
class Singleton {
public:
static Singleton* GetInstance() {
if (_instance == nullptr) {
_instance = new Singleton();
}
return _instance;
}
private:
Singleton() {} // 构造 为了保证全局只有一个
~Singleton() {} // 避免直接被删除
Singleton(const Singleton& clone) {} // 拷贝构造, 禁止拷贝构造
Singleton& operator=(const Singleton&) {}
static Singleton* _instance;
};
Singleton* Singleton::_instance = nullptr; // 静态成员需要初始化
有啥子问题?
-
首先说一下,
_instance是通过new Singleton()生成的所以是存放在堆上的, 然后_instance又是static的; 当单例随着进程退出的时候,_instance的析构函数没有被调用, 所以存在内存泄漏的问题 -
还有线程安全问题
版本2
#include <stdlib.h>
class Singleton {
public:
static Singleton* GetInstance() {
if (_instance == nullptr) {
_instance = new Singleton();
atexit(Destructor);
}
return _instance;
}
~Singleton() {}
private:
static void Destructor() {
if (nullptr != _instance) { // 静态成员变量, 是所有类对象共享的
delete _instance;
_instance = nullptr;
}
}
Singleton();
Singleton(const Singleton& cpy);
Singleton& operator=(const Singleton&) {}
static Singleton* _instance;
};
Singleton* Singleton::_instance = nullptr; // 静态成员需要初始化
没有考虑到多线程
版本3
#include <mutex>
class Singleton { // 懒汉式
public:
static Singleton* GetInstance() {
// std::lock_guard<std::mutex> lock(_mutex); // 3.1 切换现场
if (_instance == nullptr) {
std::lock_guard<std::mutex> lock(_mutex); // 3.2
if (_instance == nullptr) {
// 1. 分配内存
// 2. 调用构造函数
// 3. 返回指针
// 4. 多线程环境下, CPU会优化 reorder, 执行顺序会变成 1 3 2, 可能没有完成初始化就返回了, 会出现野指针
_instance = new Singleton();
std::atexit(Destructor);
}
}
return _instance;
}
private:
static void Destructor() {
if (nullptr != _instance) {
delete _instance;
_instance = nullptr;
}
}
Singleton(){}
Singleton(const Singleton& cpy) {}
Singleton& operator=(const Singleton&) {}
static Singleton* _instance;
static std::mutex _mutex;
};
Singleton* Singleton::_instance = nullptr; // 静态成员需要初始化
std::mutex Singleton::_mutex; // 互斥锁初始化
增加双检测的时候能看好的保证线程安全和执行效率, 使用了3.1版本和没有做双检测都保证不了
但是要考虑一下多线程环境下, CPU reorder可能会导致初始化对象的时候问题, 导致产生了一个空的单例对象
版本4
#include <mutex>
#include <atomic>
class Singleton {
public:
static Singleton* GetInstance() {
Singleton* tmp = _instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); // 获取内存屏障 // 会让new按顺序操作, 不会让new重排
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_release); // 释放内存屏障
_instance.store(tmp, std::memory_order_relaxed);
std::atexit(Destructor);
}
}
return tmp;
}
private:
static void Destructor() {
Singleton* tmp = _instance.load(std::memory_order_relaxed);
if (nullptr != tmp) {
delete tmp;
}
}
Singleton(){}
Singleton(const Singleton&) {};
Singleton& operator=(const Singleton&) {}
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
这个就能解决版本3的问题了, 就不会出现这个reorder new的问题
版本5
class Singleton {
public:
~Singleton(){}
static Singleton& GetInstance() {
static Singleton instance;
return instance;
}
private:
Singleton(){}
Singleton(const Singleton&) {}
Singleton& operator=(const Singleton&) {}
};
来来来膜拜一下大佬的代码, 这个版本就是effective作者自己写的
C++11 static的新特性: 如果当变量在初始化的时候, 并发同时进入声明语句, 并发线程将会阻塞等待初始化结束
- 利用静态局部变量特性, 延迟加载
- 利用静态全局变量特性, 系统自动回收内存, 自动调用析构函数
- 静态全局变量初始化时, 没有new操作带来的cpu指令reorder操作
但是这还是不够完美, 他的可拓展性太差
版本6
template<typename T>
class Singleton {
public:
static T& GetInstance() {
static T instance; // 这里要初始化DesignPattern, 需要调用DesignPattern构造函数, 同时会调用父类的构造函数
}
protected:
virtual ~Singleton() {}
Singleton() {} // protected修饰构造函数, 才能让别人继承
Singleton(const Singleton&) {}
Singleton& operator=(const Singleton&) {}
};
class DesignPattern : public Singleton<DesignPattern> {
friend class Singleton<DesignPattern>; // friend能让Singleton<T>访问到DesignPattern构造函数
public:
~DesignPattern() {}
private:
DesignPattern(const DesignPattern&) {}
DesignPattern& operator=(const DesignPattern&) {}
};
这样就解决了方案5的问题