【设计模式】单例模式---C++

简单说,一个类只能创建一个唯一的实例就叫单例模式

我们在创建一个类的时候,需要将拷贝构造函数禁用掉

单例模式分成两种模式----饿汉式和懒汉式

饿汉式----在创建类的时候就将其实例创建出来了,后面需要的时候直接用

// 饿汉模式---在创建类的时候就将实例初始化了
class taskqueue {
public:
    // 禁用构造函数和拷贝构造函数
    //taskqueue() = delete;                  // 禁用构造函数
    taskqueue(const taskqueue& t) = delete;  // 禁用拷贝构造函数
    taskqueue& operator=(const taskqueue& t) = delete; // 禁用拷贝构造函数
    static taskqueue* getinstance() {        // 类特有的,不是对象特有
        return m_task;
    }
    void print() {
        cout << "这是单例模式。。。" << endl;
    }
    void printInstanceAddr() {
        // 非静态函数访问静态成员变量instance
        cout << "当前单例实例地址:" << m_task << endl;
    }
private:
    // 设置成私有的,外部一样访问不到(另一种方式)
    taskqueue() = default;    // 类的外部不能访问,类的外部不能创建对象了
    //taskqueue(const taskqueue& t) = default;
    //taskqueue& operator=(const taskqueue& t) = default;
    // 只能通过类名访问静态属性或方法
    // 静态的成员变量是不能在类的内部进行初始化的
    static taskqueue* m_task;       // 类特有的,不是对象特有的
};
taskqueue* taskqueue::m_task = new taskqueue;  // 构造了唯一的一个实例对象

懒汉式—在类创建的时候不会将实例创建出来,而是等到用的时候再去创建

// 懒汉模式----什么时候用实例对象,什么时候创建
class taskqueue {
public:
    // 禁用构造函数和拷贝构造函数
    //taskqueue() = delete;                  // 禁用构造函数
    taskqueue(const taskqueue& t) = delete;  // 禁用拷贝构造函数
    taskqueue& operator=(const taskqueue& t) = delete; // 禁用拷贝构造函数
    static taskqueue* getinstance() {        // 类特有的,不是对象特有
        if (m_task == nullptr) {
            m_task = new taskqueue;
        }
        return m_task;
    }
    void print() {
        cout << "这是单例模式。。。" << endl;
    }
    void printInstanceAddr() {
        // 非静态函数访问静态成员变量instance
        cout << "当前单例实例地址:" << m_task << endl;
    }
private:
    // 设置成私有的,外部一样访问不到
    taskqueue() = default;    // 类的外部不能访问,类的外部不能创建对象了
    //taskqueue(const taskqueue& t) = default;
    //taskqueue& operator=(const taskqueue& t) = default;
    // 只能通过类名访问静态属性或方法
    // 静态的成员变量是不能在类的内部进行初始化的
    static taskqueue* m_task;       // 类特有的,不是对象特有的
};
taskqueue* taskqueue::m_task = nullptr;  // 构造了唯一的一个实例对象

如果在单线程的环境下,这两种模式是一样的。

如果运行在多线程环境下,饿汉式不会造成线程安全问题,懒汉式会造成线程安全问题

懒汉式的线程安全问题

例如
    static taskqueue* getinstance() {        // 类特有的,不是对象特有
        if (m_task == nullptr) {
            m_task = new taskqueue;
        }
        return m_task;
    }
 问题:有几个线程同时运行这段代码,大家都发现指针为空,则大家都会创建一个新的对象,则会导致出现线程安全问题
 

解决的方法:

1、双重检查锁定
    static taskqueue* getinstance() {        // 类特有的,不是对象特有
    	if(m_task == nullptr)
    	{
    	  m_mutex.lock();
          if (m_task == nullptr) {
           	m_task = new taskqueue;
       	  }
       	  m_mutex.unlock();
    	}
        return m_task;
    }
只有第一批线程会进行争抢锁资源进行加锁解锁处理,往后的线程会直接返回

2、上面那种也会出现问题,实际上 m_taskQ = new TaskQueue; 在执行过程中对应的机器指令可能会被重新排序,所以需要加上用原子操作解决。

3、使用静态局部对象(只有c++11版本之后才可以用)
class TaskQueue
{
public:
    // = delete 代表函数禁用, 也可以将其访问权限设置为私有
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance()
    {
        static TaskQueue taskQ;//静态局部队列对象,并且将这个对象作为了唯一的单例实例。
        return &taskQ;
    }
    void print()
    {
        cout << "hello, world!!!" << endl;
    }

private:
    TaskQueue() = default;
};
int main()
{
    TaskQueue* queue = TaskQueue::getInstance();
    queue->print();
    return 0;
}

为什么静态局部对象是可以的?

运行一段代码需要几个区域共同协作:
全局数据区,栈区,堆区,代码区,常量区;

C++11 标准明确规定:局部静态变量的初始化会在第一次个访问它的线程中完成,且初始化过程是线程安全的 —— 即多个线程同时访问时,只会有一个线程负责初始化,其他线程会阻塞等待初始化完成,且初始化仅执行一次。
当多个线程同时调用 getInstance() 时,C++11 编译器会自动为初始化过程添加隐式锁:
第一个到达的线程会执行初始化逻辑。
其他线程会在初始化完成前被阻塞,等待初始化结束。
初始化完成后,所有线程访问的都是同一个已初始化的实例。

static修饰的变量是存储在全局数据区的,全局数据区分为“显式初始化”和“未显式初始化
显式初始化:例如:int a = 2;
未显示初始化:例如:int a; int a = 0;(因为我们默认是0,所以系统初始化为0)

所以static TaskQueue taskQ;存放在全局数据区,未显示初始化,系统就会将其“内存初始化”。

一、第一步:系统(操作系统 / 运行时)完成 “内存初始化”

全局数据区(静态存储区)的所有变量,在程序加载到内存时,操作系统或运行时环境会先执行 “零值初始化”—— 将变量所在的内存空间清零(比如指针置为 nullptr、数值类型置为 0、类对象的内存区域初始化为全 0 字节)。
这一步是系统自动完成的,无论变量是基本类型还是类类型,只要存放在全局数据区,都会经历这个过程。对于 taskQ 来说,系统会先为其分配一块符合 TaskQueue 类大小的内存,并将这块内存的所有字节初始化为 0,确保内存没有 “垃圾值”。

二、第二步:编译器确保 “类构造函数执行”(完成对象初始化)

仅完成内存清零还不够 —— 类对象的初始化核心是执行构造函数(初始化对象的成员、建立对象的合法状态)。这一步由编译器通过代码逻辑保证,具体规则如下:
初始化时机:静态局部变量 taskQ 不会在程序启动时立即执行构造函数,而是在首次调用 getInstance() 函数时才触发构造(符合懒汉模式 “延迟初始化” 的需求)。

构造函数调用:由于在 TaskQueue 类中声明了 TaskQueue() = default;(编译器生成默认构造函数),当首次访问 taskQ 时,编译器会自动插入代码调用这个默认构造函数,完成 taskQ 对象的初始化(即使类中没有成员变量,构造函数也会正常执行,标志对象 “合法创建”)。

线程安全保证(C++11+):编译器会为这个 “首次初始化” 过程添加隐式锁,确保多线程环境下只有一个线程执行构造函数,避免重复初始化 —— 这也是编译器层面对静态局部变量初始化的保障。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值