C++ 单例模式
什么是单例模式?
一种创建型的设计模式,该模式的主要目的就是确保某个类有且仅有一个实例存在。
单例模式有三个关键点:
1、单例类只能有一个实例。
为此,单例类只能提供私有的构造函数,即保证不能随意创建该类的实例。
2、单例类必须自己创建自己的唯一实例。
因为构造函数是私有的,其他对象不能创建单例类的实例,只能是单例类自己来创建。
3、单例类必须给所有其他对象提供这一实例。
外界需要获取并使用这个单例类的实例,但是由于该类的构造函数是私有的,外界无法通过new去获取它的实例,那么就必须提供一个静态的公有方法,该方法创建或者获取它本身的静态私有对象并返回。
单例模式有两种实现方式:
懒汉式:故名思义,懒汉很懒只有饿了才会去找吃的。也就是说,只有在需要使用的时候才会去实例化。
饿汉式:饿了肯定要饥不择食。在单例类定义的时候就进行实例化。
懒汉式单例模式
//Singleton.h #pragma once class Singleton { public: static Singleton* getInstance(); private: Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&); static Singleton* instance_; }; //Singleton.cpp #include <iostream> #include "Singleton.h" Singleton* Singleton::instance_ = NULL; Singleton::Singleton() { } Singleton::Singleton(const Singleton&) { } Singleton & Singleton::operator=(const Singleton&) { } Singleton * Singleton::getInstance() { if (NULL == instance_) { instance_ = new Singleton(); } return instance_; }
(1)默认构造函数是私有的,外部不能进行单例类的实例化;
(2)拷贝构造函数和赋值运算符也是私有的,以禁止拷贝和赋值;
(3)具有一个私有的静态成员指针 instance_,指向唯一的实例;
(4)提供一个公有的静态成员函数用于返回实例,如果实例为NULL,则进行实例化。
饿汉式单例模式
//Singleton.h #pragma once class Singleton { public: static Singleton* getInstance(); private: Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&); static Singleton* instance_; }; //Singleton.cpp #include <iostream> #include "Singleton.h" Singleton* Singleton::instance_ = new Singleton(); Singleton::Singleton() { } Singleton::Singleton(const Singleton&) { } Singleton & Singleton::operator=(const Singleton&) { } Singleton * Singleton::getInstance() { return instance_; }
与懒汉式单例模式不同之处是,在全局作用域进行单例类的实例化,并用此实例初始化单例类的静态成员指针instance_。
线程安全问题
懒汉式:如果有两个线程同时获取单例类的实例,都发现实例不存在,因此都会进行实例化,就会产生两个实例都要赋值给instance_,这是严重的错误。为了解决这个问题,就要考虑加锁。
线程安全的懒汉式,修改获取实例的方法如下:
Singleton * Singleton::getInstance() { lock(); //上锁 if (NULL == instance_) { instance_ = new Singleton(); } unlock(); return instance_; }
但这个获取实例的方法存在性能问题,每次获取实例的时候都要先上锁,之后再解锁,如果有很多线程的话,可能会造成大量线程的阻塞。改进后的实现如下:
Singleton * Singleton::getInstance() { if (NULL == instance_) { lock(); //上锁 if (NULL == instance_) { instance_ = new Singleton(); } unlock(); } return instance_; }
绝大多数情况下,获取实例时都是直接返回实例,这时候不会涉及到上锁、解锁的问题。只有在没有实例的时候,才会涉及上锁、解锁,这种情况是很少的。这个获取实例的方法对性能几乎无影响。
饿汉式:程序运行初期就进行了单例类实例化,不存在上述的线程安全问题。
对象释放问题
上边的程序中只有new,却没有delete,也就是说只有内存申请而没有内存释放,会不会有内存泄漏?
答:一般情况下,单例类的实例都是常驻内存的,一直存在于进程的生命周期,因此不需要手动释放。如果的确需要释放实例占用的内存,一定不能在单例类的析构函数中进行delete操作,这样会造成无限循环,可以考虑增加一个destroy方法用于释放内存,或者在单例类中定义一个内嵌的垃圾回收类,详情请参考最后两个参考链接。
参考与鸣谢:
https://www.cnblogs.com/cxjchen/p/3148582.html
https://www.cnblogs.com/qiaoconglovelife/p/5851163.html
https://blog.csdn.net/stpeace/article/details/68953096
https://blog.csdn.net/zhanghuaichao/article/details/79459130
https://blog.csdn.net/chenyingying_/article/details/83029600
https://www.cnblogs.com/xzy1210/p/3849253.html