单例模式是一种创建型设计模式,它可以确保一个类在整个程序运行过程中只有一个实例,并提供一个全局访问点以获取该实例。
单例模式的核心思想就是:控制对象的实例化,防止创建多个实例,从而节省资源并保证行为一致性。
关键点:
- 单例类:包含单例实例的类,通常将构造函数声明为私有;
- 静态成员变量:用于存储单例实例的静态成员变量;
- 获取实例方法:静态方法,用于获取单例实例;
- 私有构造函数:防止外部直接实例化单例类;
- 线程安全处理:确保在多线程环境下单例实例的创建是安全的。
- 构造函数和析构函数是私有的,不允许外部生成和释放
- 静态成员变量和静态返回单例的成员函数
- 禁用拷贝构造和赋值运算符
单例类主要是通过一个公共的静态方法getinstance接口,用于获取该类的实例,如果实例不存在,则在该方法内部创建实例并返回。
也就是说,单例类的构造方法不让其他人进行修改和使用;并且单例类只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,就只能进行调用,确保了全局只创建一个实例。
基本写法:
下面是一个最基础的实现:
cpp
class Singleton
{
private:
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 禁用拷贝构造
Singleton& operator = (const Singleton&) = delete; // 禁用赋值操作符
public:
static Singleton* getInstance() {
static Singleton instance;
return &instance;
}
void show() {
cout << "Singleton Show" << endl;
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;
int main() {
Singleton::getInstance()->show();
return 0;
}
其实他有两种方式懒汉模式和饿汉模式
懒汉模式
其核心是延迟初始化,可以理解为它很懒,所以它一直没有初始化,只有在首次调用getInstance()时才创建单例实例。
- 优点:节省资源,单例对象未被使用,则不会创建。
- 缺点:需要考虑线程安全的问题,多线程下可能会重复创建。
饿汉模式
其核心是提前初始化,即在程序启动时直接创建单例实例,无论是否被使用。
- 优点:线程安全,实例在程序启动时初始化,避免多线程竞争。
- 缺点:浪费资源,即使没有使用单例对象,也会占用内存。
实际开发中建议使用C++11的局部静态变量懒汉模式
实现过程分析
下面是针对懒汉模式的一个实现过程的分析
示例1
cpp
class Singleton1 {
public:
// 要点2
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;
// 要点2
static Singleton1 *_instance;
};
Singleton1* Singleton1::_instance = nullptr;
错误的点:_instance = new Singleton1();堆上的资源不能得到正确的析构,资源还是可以释放,但是在释放的时候是无法调用析构函数的。
示例2
cpp
class Singleton2 {
public:
static Singleton2 * GetInstance() {
if(_instance == nullpte) {
_instance = new Singleton2();
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;
static Singleton2 *_instance;
};
Singleton2* Singleton2::_instance = nullptr;
相对于示例1,添加了一个atexit()方法,这个方法就是程序退出的时候,它会去调用Destructor函数,就可以在这个函数里面实现一个手动的析构
示例3
cpp
class Singleton3 {
public:
static Singleton3 * GetInstance() {
std::lock_guard<std::mutex> lock(_mutex);
if(_instance == nullptr) {
std::lock_guard<std::mutex> lock(_mutex);
if(_instance == nullptr) {
_instance = new Singleton3();
// 1. 分配内存
// 2. 调用构造函数
// 3. 返回对象指针
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;
static Singleton3 *_instance;
static std::mutex _mutex;
};
Singleton3* Singleton3::_instance = nullptr;
std::mutex Singleton3::_mutex;
要实现一个线程安全的单例模式,如果只用if外面那个锁,它是单检测的情况下,它总是返回那个_instance是线程安全的,缺点是,new Singleton3()只会有一次,其他情况都是拿到instance然后进行返回。两个if中间加锁是为了防止有两个线程同时进入而导致new了两次。但是也不对,没有考虑到多线程情况下,指令重排的问题。
示例4
cpp
class Singleton4 {
public:
static Singleton4 * GetInstance() {
Singleton4* tmp = _instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_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);
_instance.store(tmp, std::memory_order_relaxed);
atexit(Destructor);
}
}
return tmp;
}
private:
static void Destructor() {
Singleton4* tmp = _instance.load(std::memory_order_relaxed);
if(nullptr != tmp) {
delete tmp;
}
}
Singleton4() {}
~Singleton4() {
std::cout << "~Singleton4()\n";
}
Singleton4(const Singleton4 &) = delete;
Singleton4& operator = (const Singleton4&) = delete;
Singleton4(Singleton4 &&) = delete;
Singleton4& operator = (Singleton4 &&) = delete;
static std::atomic<Singleton4*> _instance;
static std::mutex _mutex;
};
std::atomic<Singleton4*> Singleton4::_instance;
std::mutex Singleton4::_mutex;
强制限制指令重排。
里面加入了获取屏障acquire fence和释放屏障release fence,也就后续的读/写操作不会重排到屏障前,且能读取其他线程的释放操作结果;释放屏障前面的读/写操作不会重排到屏障后,且保证当前线程的写入对其他线程可见。
示例5
cpp
class Singleton5 {
public:
static Singleton5* GetInstance() {
static Singleton5 instance;
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;
};
这个版本是最简单的,如果只是让你简单实现一个单例模式,可以直接写这个版本。
示例6
cpp
template<typename T>
class Singleton {
public:
static T* GetInstance() {
static T instance;
return &instance;
}
protected:
Singleton() {}
virtual ~Singleton() {
std::cout << "~Singleton()\n";
}
private:
Singleton(const Singleton &) = delete;
Singleton& operator = (const Singleton&) = delete;
Singleton(Singleton &&) = delete;
Singleton& operator = (Singleton &&) = delete;
};
class DesignPattern : public Singleton<DesignPattern> {
friend class Singleton<DesignPattern>;
private:
DesignPattern() {}
~DesignPattern() {
std::cout << "~DesignPattern()\n";
}
};
使用类模板把三个要点进行封装
总结:
版本1:堆上资源不能正确析构(没有调用析构函数)
版本2:堆上资源能正确析构(调用了析构函数)
版本3:双检查锁,可能造成内存泄露
版本4:线程安全,原子操作+互斥锁+内存屏障
版本5:C++11静态局部变量具备线程安全特性延迟加载内存正确释放
版本6:模板类封装了单例要点