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

server/2024/9/22 21:57:51/

前言

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

正文

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

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

创建
// Singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class Singleton; }
QT_END_NAMESPACEclass Singleton : public QWidget
{Q_OBJECTpublic: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_OBJECTpublic: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_OBJECTpublic: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.ppmy.cn/server/120481.html

相关文章

pytorch实现RNN网络

目录 1.导包 2. 加载本地文本数据 3.构建循环神经网络层 4.初始化隐藏状态state 5.创建随机的数据&#xff0c;检测一下代码是否能正常运行 6. 构建一个完整的循环神经网络 7.模型训练 8.个人知识点理解 1.导包 import torch from torch import nn from torch.nn imp…

如何将生物序列tokenization为token?

文章目录 原理讲解&#xff1a;代码实现&#xff1a;利用k-mer技术把核苷酸或氨基酸序列tokenization成token输入文件和结果文件&#xff1a; 原理讲解&#xff1a; tokenization是自然语言处理领域非常成熟的一项技术&#xff0c;tokenization就是把我们研究的语言转换成计算…

macOS平台(intel)编译MAVSDK安卓平台SO库

1.下载MAVSDK: git clone https://github.com/mavlink/MAVSDK.git --recursive 2.编译liblzma 修改CMakeLists.txt文件增加C与CXX指令-fPIC set(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}") set(CMAKE_CXX_FLAGS "-fPIC ${CMAKE_CXX_FLAGS}") 修改如下:…

算法题目复习(0909-0917)

1. 连续子序列和 pdd的算法题&#xff0c;根本不记得怎么做 给一个数组&#xff0c;有正数和负数&#xff0c;算出连续子序列的和最大为多少 int maxSubArraySum(vector<int>& nums) {int maxSoFar nums[0];int maxEndingHere nums[0];for (size_t i 1; i <…

Ubuntu 22.04.5 LTS 发布下载 - 现代化的企业与开源 Linux

Ubuntu 22.04.5 LTS (Jammy Jellyfish) - 现代化的企业与开源 Linux Ubuntu 22.04.5 发布&#xff0c;配备 Linux 内核 6.8 请访问原文链接&#xff1a;https://sysin.org/blog/ubuntu-2204/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xf…

【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】007 - 一号内核线程 kernel_init线程 工作流程分析

【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】007 - 一号内核线程 kernel_init线程 工作流程分析 系列文章汇总:《鸿蒙OH-v5.0源码分析之 Uboot+Kernel 部分】000 - 文章链接汇总》 本文链接:《【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】007 - 一号内核线程 kernel_init线…

nginx_单机平滑升级

#!/bin/bash# 定义要下载的 Nginx 源码包的 URL 和保存路径 nginx_tar"http://nginx.org/download/nginx-1.19.0.tar.gz" nginx_tar_file"/tmp/nginx-1.19.0.tar.gz" nginx_version"nginx-1.19.0" nginx_path$(which nginx) # 获取 Nginx 的路径…

【Kubernetes】常见面试题汇总(二十四)

目录 71.假设一家公司想要修改它的部署方法&#xff0c;并希望建立一个更具可扩展性和响应性的平台。您如何看待这家公司能够实现这一目标以满足客户需求&#xff1f; 72.考虑一家拥有非常分散的系统的跨国公司&#xff0c;期待解决整体代码库问题。您认为公司如何解决他们的问…