设计模式之单例模式(C++)

一句话描述

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

理解

  1. “确保一个类只有一个实例”

    • 这点就要求其构造方法不能是public公开的,一定是不能随便被外面所调用,即不能被外界进行实例化,则它的构造方法只能是private
  2. “而且自行实例化并向整个系统提供这个实例”

    • 只有一个实例的情况下,向整个系统提供这个实例,那么这个实例是必属于这个类的,所以这个实例是类成员变量,即一定是静态变量

    • 而且,要向整个系统提供这个实例,那么只能是一个

      静态成员方法

      C++中,静态变量只能通过静态成员方法访问。

作用

显而易见,确保一个类只有一个实例存在,比如序号生成计数器全局的管理类,在软件架构中应用广泛。

两种实现

根据单例类对象实例化时间段,对单例类的实现,可以分为两类。

饿汉式

#include <iostream>

class Only {
    
    
public:
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        return m_singleton;
    }
    
private:
    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* m_singleton;
}
// 静态成员变量(属性)类外初始化,注意需要在cpp文件初始化
Only* Only::m_singleton = new Only();

显而易见,饿汉式的实现在程序加载时就完成,比较着急,所以称为饿汉式。

懒汉式

#include <iostream>

class Only {
    
    
public:
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        if(NULL == m_singleton) {
    
    
            m_singleton = new Only();
        }
        return m_singleton;
    }
    
private:
    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* m_singleton;
}
// 静态成员变量(属性)类外初始化
Only* Only::m_singleton = NULL;

显而易见,懒汉式的实例初始化在第一次调用获得类实例接口时,不调用则不初始化,比较懒。

双重检查锁

懒汉式的实现原理可以料想到,在多线程的情况下,可能会遇到static Only* GetInstance()方法,同时被多个线程调用的情况,这样就会static Only* singleton就会被初始化多次,从而发生内存泄漏,那怎么办呢?加锁

#include <iostream>
#include <mutex>

// 锁
mutex m_Only_mutex;

class Only {
    
    
public:
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        m_Only_mutex.lock();
        if(NULL == m_singleton) {
    
    
            m_singleton = new Only();
        }
        m_Only_mutex.unlock();
        return m_singleton;
    }
    
private:
    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* m_singleton;
}
// 静态成员变量(属性)类外初始化
Only* Only::m_singleton = NULL;

貌似这样就可以了,但是,其实有两个可以优化的地方:

  1. 锁范围可以进一步缩小,毕竟static Only* GetInstance(),这个接口可能会被调用多次,所以应该判空语句内部加锁,那么可能同时多个线程运行到判空语句,所以在互斥区的第一句中还需要判空检查一次,这就是双重检查锁

  2. 在多线程使用时,对于当前的单例类实例对象应该加volatile修饰,为什么呢?因为

    m_singleton = new Only();
    

    可以拆分为3步:

    1. 分配内存
    2. 初始化对象
    3. m_singleton指向刚分配的内存

这个操作过程其实是非原子操作的,也就是说,在这过程中,CPU可能会发生重排序的情况。比如:线程A已经执行了步骤1和步骤3,还未执行步骤2,这时候m_singleton已经不为空了,假如线程B前,调用static Only* GetInstance()那么B将得到一个没有初始化,但有值的m_singleton,所以,应该使用volatile修饰,避免重排序。

#include <iostream>
#include <mutex>

// 锁
mutex m_Only_mutex;

class Only {
    
    
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        // 双重检查锁
        if(NULL == m_singleton) {
    
    
            m_Only_mutex.lock();
            if(NULL == m_singleton) {
    
    
                m_singleton = new Only();
            }
        }
        m_Only_mutex.unlock();
        return m_singleton;
    }
    
private:
    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* volatile m_singleton;
}
// 静态成员变量(属性)类外初始化
Only* Only::m_singleton = NULL;

内存回收优化

对于单例类的实例来讲,有时候必须要将当前实例释放掉,比如关闭文件,释放外部资源。但由于实例是静态成员变量,在类的析构行为中无法实现这个要求。我们需要一种方法,正常地删除该实例。

  1. 可以在程序结束时调用

    GetInstance()
    

    并对返回的指针调用delete操作。

    这样做可以实现功能,不是很优美,而且容易出错。因为这样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance()(针对于饿汉式)。

  2. 让这个类自己知道在合适的时候把自己删除。或者说把删除自己的操作挂在系统中的某个合适的点上,使其在恰当的时候自动被执行。

    程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。所以,利用这个特性,实现单例类实例,在程序结束时自动释放资源。

#include <iostream>
#include <mutex>

// 锁
mutex m_Only_mutex;

class Only {
    
    
    // 提供类实例的唯一接口,静态成员方法
    static Only* GetInstance() {
    
    
        // 双重检查锁
        if(NULL == m_singleton) {
    
    
            m_Only_mutex.lock();
            if(NULL == m_singleton) {
    
    
                m_singleton = new Only();
            }
        }
        m_Only_mutex.unlock();
        return m_singleton;
    }
    
private:
    class AutoRelease {
    
    
	public:
		AutoRelease() {
    
    }
        // 通过AutoRelease类的析构函数,释放m_singleton
		~AutoRelease() {
    
    
			if(m_singleton) {
    
    
				delete m_singleton;
			}
		}
	};

    // 类构造函数一定是private修饰
    Only () {
    
    };
    
    // 类实例对象一定是一个静态变量
    static Only* volatile m_singleton;
    // 类静态变量,用于程序退出时,单例类自动析构
    static AutoRelease _autoRelease;
}
// 静态成员变量(属性)类外初始化
Only* Only::m_singleton = NULL;
Only::AutoRelease Only::_autoRelease;

参考目录

  • https://www.bilibili.com/video/BV1af4y1y7sS?spm_id_from=333.999.0.0
  • https://blog.csdn.net/qq_29542611/article/details/79301595
  • https://blog.csdn.net/weixin_44363885/article/details/92838607
  • https://bbs.csdn.net/topics/600461189
  • 单例模式内存释放

猜你喜欢

转载自blog.csdn.net/suren_jun/article/details/128260164