当前位置: 首页 > article >正文

Qt:懒汉单例(附带单例使用和内存管理)

前言

本文主要写懒汉单例以及单例的释放,网上很多教程只有单例的创建,但是并没有告诉我们单例的内存管理,这就很头疼。

正文

以下是两种懒汉单例的写法

1. 懒汉式单例(多线程不安全,但是在单线程里面是安全的)

创建
// Singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Singleton; }
QT_END_NAMESPACE

class Singleton : public QWidget
{
    Q_OBJECT

public:
    static Singleton* getInstance();
private:
    // 私有化构造函数,防止外部创建实例
    Singleton(QWidget *parent = nullptr);

   // 禁止拷贝构造和赋值操作
   Singleton(const Singleton&) = delete;
   Singleton& operator=(const Singleton&) = delete;
    ~Singleton();

private:
    Ui::Singleton *ui;
    // 创建静态指针变量
    static Singleton* instance;
};
#endif // SINGLETON_H


// Singleton.cpp
#include "singleton.h"
#include "ui_singleton.h"
#include "qdebug.h"
// 静态变量需要在类外进行初始化
Singleton* Singleton::instance = nullptr;

Singleton *Singleton::getInstance()
{
    if (instance == nullptr) {
        // 使用构造函数
        instance = new Singleton();
    }
    return instance;
}

Singleton::Singleton(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Singleton)
{
    ui->setupUi(this);

}

Singleton::~Singleton()
{
    qDebug()<<"单例安全销毁";
    delete ui;
}

解释:

  • 懒汉式在第一次调用时创建实例,延迟初始化。但未加锁,在多线程环境下不安全。

使用

//UseSingleton.h
#ifndef USESINGLETON_H
#define USESINGLETON_H

#include <QWidget>

namespace Ui {
class UseSingleton;
}

class UseSingleton : public QWidget
{
    Q_OBJECT

public:
    explicit UseSingleton(QWidget *parent = nullptr);
    ~UseSingleton();

private slots:
	// 这里我在UseSingleton.ui中添加了一个按钮,用于创建单例
    void on_pushButton_clicked();

private:
    Ui::UseSingleton *ui;
};

#endif // USESINGLETON_H


//UseSingleton.cpp
#include "usesingleton.h"
#include "ui_usesingleton.h"
#include "singleton.h"
UseSingleton::UseSingleton(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::UseSingleton)
{
    ui->setupUi(this);
}

UseSingleton::~UseSingleton()
{
    delete ui;
}

void UseSingleton::on_pushButton_clicked()
{
	// 创建单例,但是这里是局部变量,只能在这里使用,也可以将创建一个单例类的成员对象
    Singleton* instance = Singleton::getInstance();
    instance->show();
}

内存管理

此处的单例类是作为局部变量来创建的,在更安全的懒汉中我将单例类作为成员变量来创建来展示内存管理。

  • 1.当单例是一个窗口类时,我们可以重写closeEvent来管理内存,即使得窗口关闭时,销毁单例,代码如下
// 在Singleton类中添加如下代码
void Singleton::closeEvent(QCloseEvent *)
{
    // 销毁对象
    instance->deleteLater();
    // 指针置空非常重要
    instance = nullptr;
}
程序运行结果

当我通过按钮重复创建对象后,并且关闭单例窗口类时,单例能安全销毁。
在这里插入图片描述

  • 1.1当单例是窗口类时,我们也可以通过,设置Qt::WA_DeleteOnClose属性来管理内存,代码如下:
// 在Singleton构造函数中添加
Singleton::Singleton(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Singleton)
{
    ui->setupUi(this);
    // 添加
    this->setAttribute(Qt::WA_DeleteOnClose,true);

}
// 在析构函数中添加
Singleton::~Singleton()
{
    qDebug()<<"单例安全销毁";
    // 添加置空,置空非常重要
    instance = nullptr;
    delete ui;
}

程序运行结果

当我通过按钮重复创建对象后,并且关闭单例窗口类时,单例能安全销毁。
在这里插入图片描述

注意:当你按照我以上的方法管理内存时,你就不要更改我的单例,不要在栈上创建单例,否则delete栈上的空间程序直接崩溃不要来找我。

  • 1.2自己管理内存。
    这个你参考下面单例是非窗口类中的自己管理内存吧,都一样.
    这里说明下,为什么每次销毁完对象要指针置空,因为我们存储对象的指针是静态的,所以初始化的时候只会初始化一次,要是你的单例是主程序还好,像上面我的类中单例类并不是主程序,使用单例类的类才是主程序,所以当我将单例对象销毁后(此时主程序并没有结束),再次创建单例对象的时候,程序就会崩溃,因为我的指针并不是空的,它就不会执行new那一部分,而是直接返回一个空的内容,所以程序会崩溃。感兴趣的朋友可以自己尝试下,或者我们私下交流下。
  • 2 当单例类不是窗口类的时候,我们可以自己管理内存,具体实现是自己写一个销毁单例的函数,如下
// 新建一个没有窗口的类
// SingletonNoUi.h
#ifndef SINGLETONNOUI_H
#define SINGLETONNOUI_H

#include <QObject>

class SingleTonNoUi : public QObject
{
    Q_OBJECT
public:
    static SingleTonNoUi* getInstance();
    // 销毁单例
    static void destoryInstance();
private:
    explicit SingleTonNoUi(QObject *parent = nullptr);
    // 禁止拷贝构造和赋值操作
    SingleTonNoUi(const SingleTonNoUi&) = delete;
    SingleTonNoUi& operator=(const SingleTonNoUi&) = delete;
	 ~SingleTonNoUi();
signals:

private:
    // 创建静态指针变量
    static SingletonNoUi* instance;
};

#endif // SINGLETONNOUI_H



// SingletonNoUi.cpp
#include "singletonnoui.h"
#include "qdebug.h"
// 初始化静态变量
SingleTonNoUi*SingleTonNoUi::instance = nullptr;

SingleTonNoUi *SingleTonNoUi::getInstance()
{
    if (instance == nullptr) {
        instance = new SingleTonNoUi();
    }
    return instance;
}
void SingleTonNoUi::destoryInstance()
{
    if (instance) {
        instance->deleteLater();
        // 指针置空非常重要
        instance = nullptr;
    }
}
SingleTonNoUi::~SingleTonNoUi()
{
    qDebug()<<"非窗口单例类安全销毁";
}

SingleTonNoUi::SingleTonNoUi(QObject *parent) : QObject(parent)
{
    qDebug()<<"非窗口单例创建成功";
}

//在UseSingleton中再添加一个按钮,转到槽;在槽函数中添加
void UseSingleton::on_pushButton_2_clicked()
{
    SingleTonNoUi* instance = SingleTonNoUi::getInstance();
    /*
        其它处理逻辑
    */
    instance->destoryInstance();
}

程序运行结果

刚创建会被直接销毁
在这里插入图片描述

  • 2.1使用智能指针来管理内存,但是这种方法需要对原先的单例做出一些改变,代码如下
// SingletonNoUi.h
#ifndef SINGLETONNOUI_H
#define SINGLETONNOUI_H

#include <QObject>
#include <QScopedPointer>

class SingleTonNoUi : public QObject
{
    Q_OBJECT
public:
    static SingleTonNoUi* getInstance();
    // 需要将析构函数声明为public,要不然智能指针管理不了
    ~SingleTonNoUi();
private:
    explicit SingleTonNoUi(QObject *parent = nullptr);
    // 禁止拷贝构造和赋值操作
    SingleTonNoUi(const SingleTonNoUi&) = delete;
    SingleTonNoUi& operator=(const SingleTonNoUi&) = delete;

signals:

private:
    // 创建静态指针变量
    static QScopedPointer<SingleTonNoUi> instance;
};

#endif // SINGLETONNOUI_H



// SingletonNoUi.cpp
#include "singletonnoui.h"
#include "qdebug.h"
// 初始化静态成员变量,此处不能赋予nullptr
QScopedPointer<SingleTonNoUi> SingleTonNoUi::instance;

SingleTonNoUi *SingleTonNoUi::getInstance()
{
    if(instance.isNull()) {
        instance.reset(new SingleTonNoUi());
    }
    return instance.data();
}

SingleTonNoUi::~SingleTonNoUi()
{
    qDebug()<<"非窗口单例类安全销毁";
}

SingleTonNoUi::SingleTonNoUi(QObject *parent) : QObject(parent)
{
    qDebug()<<"非窗口单例创建成功";
}
//注意去掉UseSingleton类的槽函数的destoryInstace,即
void UseSingleton::on_pushButton_2_clicked()
{
    SingleTonNoUi* instance = SingleTonNoUi::getInstance();
    /*
        其它处理逻辑
    */
}
程序运行结果

点击创建按钮后,输出框显示非窗口单例创建成功;当我再次点击创建按钮时,没有任何变化(只要想想就会理解,因为此时我的单例类又没有被销毁,单例只能存在一个,第二个单例自然就不可能创建了),关闭主窗口,被正常销毁。
在这里插入图片描述

2. 懒汉式单例(线程安全)

// 还是和上面一样的类,只更改getInstance中的内容就行了
// Singleton.cpp中
#include <QMutex>

static Singleton* getInstance() {
        // 添加锁机制确保线程安全
        static QMutex mutex;
        if (instance == nullptr) {
        	// 加锁,确保多线程环境下的安全性,使用locker()不用显示的解锁
            QMutexLocker locker(&mutex);
            // 双重检查,防止多次创建
            if (instance == nullptr) {    
                instance = new Singleton();
            }
        }
        return instance;
}

解释:

  • 线程安全的懒汉式单例通过 QMutex 加锁,确保在多线程环境中实例只被创建一次。

使用

这里是单例类作为成员变量时的内存管理,所以要在UseSingleton中添加SingletonSingletonNoUi这两个类的成员变量,如下

#ifndef USESINGLETON_H
#define USESINGLETON_H

#include <QWidget>
#include "singleton.h"
#include "singletonnoui.h"
namespace Ui {
class UseSingleton;
}

class UseSingleton : public QWidget
{
    Q_OBJECT

public:
    explicit UseSingleton(QWidget *parent = nullptr);
    ~UseSingleton();

private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::UseSingleton *ui;
    // 添加两个类的成员变量
    Singleton* instance;
    SingleTonNoUi* instanceNoUi;
};

#endif // USESINGLETON_H

内存管理

当单例类是窗口类时:
其实和上面的单例对象作为局部变量一样。

当单例类是非窗口类时:

其实和上面的单例对象作为局部变量一样。

为什么不适用Qt中的父子机制来管理单例内存?

在Qt中,单例模式一般不使用父子机制来管理内存。因为单例模式的设计目的是保证在整个程序运行期间,某个类只有一个实例,并且它的生命周期通常贯穿整个应用程序。而Qt的父子(如QObject的父子关系)主要用于管理对象的生命周期,当父对象被销毁时,子对象也被自动销毁。单例对象的生命周期通常不与父对象绑定,所以父子机制不太适合管理单例的生命周期

小结

如有错误请指正。


http://www.kler.cn/news/305990.html

相关文章:

  • 气压测试实验(用IIC)
  • Superset二次开发之源码DependencyList.tsx 分析
  • 828华为云征文 | 云服务器Flexus X实例:部署 Gitea,拥有自己的Git仓库,管理本地代码
  • 微服务之间的安全通信
  • Xorbits Inference(Xinference):一款性能强大且功能全面的大模型部署与分布式推理框架
  • TCP/IP网络模型分层
  • PCL 点云随机渲染颜色
  • 3285、找到稳定山的下标
  • 华为CNA VRM搭建(使用vmware worfstartion搭建)
  • 【Python】开发环境配置
  • Python的Scapy库详解
  • 关于 OceanBase 4.x 中被truncate的 table 不再支持进回收站的原因
  • 聚观早报 | 2025款比亚迪汉上市;iPhone 16天猫全球同步首发
  • GEO数据的下载和处理|GEO数据转换为Gene symbol|GEO注释文件提取symbol|查看样本标签|查看GEO数据疾病或正常|生物信息基础
  • 后端开发刷题 | 矩阵的最小路径和
  • 语言模型中的多模态链式推理(论文复现)
  • CSS—4
  • Go语言现代web开发defer 延迟执行
  • 【Linux 20】进程控制
  • 网络原理 IP协议与以太网协议
  • 匹配行最大值替换为最小值公式
  • 反射是一个新的AI模型,可以在一台性能良好的笔记本上运行并在测试中击败GPT-4o
  • matlab while (~feof(fid))语句解释
  • 【C++11】智能指针
  • AMD FSR 4已秘密开发1年 支持AI帧生成
  • opencv之图像梯度
  • Android实现关机和重启功能
  • Linux开发讲课43---/proc/net/dev文件内容详解
  • springboot-创建连接池
  • 【第36章】Spring Cloud之Seata分布式事务