【前言】最近看公司的代码,好多项目里面使用了单例模式。不会涉及公司具体代码,仅对单例模式做一个学习总结。
一、C++单例模式
通过单例模式可以保证系统中只有一个类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。对于系统中的某些类来说,只有一个实例很重要,比如一个打印机可以有多个打印任务,但是只有一个正在工作的任务,一个系统只能有一个窗口管理器或文件系统。
单例模式的要点有三个:1. 单例类只能有一个实例 2. 它必须自行创建这个实例 3. 它必须自行向整个系统提供提供这个实例。从具体实现角度来说,就是以下三点:1. 单例模式的类只提供私有的构造函数 2. 类定义中含有一个该类的静态私有对象 3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象
单例模式的一些注意点:
- 实例控制: 单例模式会阻止其他对象实例化自己的单例对象的副本,从而确保所有对象都访问唯一实例;
- 灵活性: 因为类控制实例化过程,所以类可以灵活更改实例化过程;
- 开销: 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销,这个问题可以通过静态初始化解决此问题:定义一个私有的静态指针m_sInstance,和一个公有的静态函数 GetInstance()。
单例模式又分为饿汉式单例和懒汉式单例,饿汉式单例在单例类被加载时就实例化一个对象交给自己的引用;而懒汉式在调用取得实例方法的时候才会实例化对象。
单例模式的优点:
- 在内存中只有一个对象,节省内存空间
- 避免频繁的创建销毁对象,可以提高性能
- 避免对共享资源的多重占用
- 可以全局访问
单例模式的适用场景:
- 需要频繁实例化然后销毁的对象
- 创建对象耗时过多或者耗资源过多,但又经常用到的对象
- 有状态的工具类对象
- 频繁访问数据库或文件的对象
- 以及其他要求只有一个对象的场景
在C++ 中一般都使用懒汉式单例,但懒汉式单例可能会有线程安全,异常安全问题。实例代码如下:
class CSingleton { private: CSingleton() //构造函数是私有的 { } public: static CSingleton * GetInstance() { static CSingleton *m_pInstance; if(m_pInstance == NULL) //判断是否第一次调用 m_pInstance = new CSingleton(); return m_pInstance; } };
对于拷贝构造和分配操作符如果我们不打算自定义的话,那么最好将它们也置为私有且不实现
考虑到线程安全和异常安全,实现代码如下:
class Lock { private: CCriticalSection m_cs; public: Lock(CCriticalSection cs) : m_cs(cs) { m_cs.Lock(); } ~Lock() { m_cs.Unlock(); } }; class CSingleton { private: CSingleton(); CSingleton(const CSingleton &); CSingleton& operator = (const CSingleton &); public: static CSingleton *Getinstance(); static CSingleton *m_Instance; static CCriticalSection cs; }; CSingleton* CSingleton::m_Instance = 0; CSingleton* CSingleton::Getinstance() { if(m_Instance == NULL) { //double check Lock lock(cs); //用lock实现线程安全,用资源管理类,实现异常安全 //使用资源管理类,在抛出异常的时候,资源管理类对象会被析构,析构总是发生的无论是因为异常抛出还是语句块结束。 if(m_Instance == NULL) { m_Instance = new Singleton(); } } return m_Instance; }
之所以在Instantialize函数里面对pInstance 是否为空做了两次判断,因为该方法调用一次就产生了对象,pInstance == NULL 大部分情况下都为false,如果按照原来的方法,每次获取实例都需要加锁,效率太低。而改进的方法只需要在第一次 调用的时候加锁,可大大提高效率。