单例模式
什么是单例模式?
单例模式是指整个系统生命周期内,保证一个类只能产生一个实例,确保该类的唯一性。
例如,Windows
中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
在计算机系统中,还有 Windows
的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web
应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式有什么特点呢?
构造函数和析构函数为私有类型,目的是禁止外部构造和析构。
- 构造函数和析构函数为私有类型,目的是禁止外部构造和析构。
- 拷贝构造函数和赋值构造函数是私有类型,目的是禁止外部拷贝和赋值,确保实例的唯一性。
- 类中有一个获取实例的静态方法,可以全局访问。
C++单例模式的三种实现
1.普通懒汉式单例(线程不安全)
懒汉式:单例实例在第一次被使用时才进行初始化。
第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Singleton { private: static Singleton* s_instance; private: Singleton() {}; ~Singleton() {}; Singleton(const Singleton&); Singleton& operator=(const Singleton&); public: static Singleton* getInstance() { if(s_instance == nullptr) s_instance = new Singleton(); return s_instance; } static void deleteInstance() { if (s_instance) { delete s_instance; s_instance = nullptr; } } };
Singleton* Singleton::s_instance = nullptr;
|
补充一下,类中static函数的作用:
在类里的函数前加static,则这个函数就与对象无关了,这个类产生的所有对象共用这一个静态成员函数,
静态成员函数可以在尚未创建任何对象时就被调用,他只可以操作类中的静态数据成员。
2.加锁的懒汉式单例(双检锁)
双检锁,既保证了效率又保证了安全。
为什么要双检锁呢?假设有两个线程同时调用getInstance()
,那么很有可能两个线程都通过了第一个判空,之后两个线程在争抢锁。这个时候里面就需要再次判空。外层的判空,是为了减少争抢锁排队的情况;内层的判空,是为了确保只new一个实例(重复new会导致内存泄漏,且违背单例模式)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include<mutex> class Singleton { private: static Singleton* s_instance; static std::mutex s_mutex;
private: Singleton() {}; ~Singleton() {}; Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; public: static Singleton* getInstance() { if(s_instance == nullptr){ std::unique_lock<std::mutex> lock(s_mutex); if(s_instance == nullptr){ s_instance = new Singleton(); } } return s_instance; } static void deleteInstance() { std::unique_lock<std::mutex> lock(s_mutex); if (s_instance) { delete s_instance; s_instance = nullptr; } } };
std::mutex Singleton::s_mutex; Singleton* Singleton::s_instance = nullptr;
|
3.饿汉模式
程序启动时就创建一个唯一的实例对象。
可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Singleton { public: static Singleton* getInstance() { return &s_instance; } private: Singleton(){}; Singleton(Singleton const&) = delete; Singleton& operator=(Singleton const&) = delete; static Singleton s_instance; }; Singleton Singleton::s_instance;
|
单例模式是否会被破坏?
- 反射(虽然C++没有这个机制,但可以了解一下)
- 序列化(反序列化的过程中可能再创一个对象)
- 克隆