Qt 智能指针介绍: QSharedPointer、QWeakPointer 、QScopedPointer 、QPointer(附实例)

news2024/11/24 9:14:14

1. 概述

在使用动态内存分配的情况下,需要确保对象的所有权正确地被管理和转移。使用智能指针可以帮助我们自动管理对象的生命周期和所有权,避免内存泄漏和悬挂指针的问题。

♦ 什么时候需要用到智能指针?

  • 在使用QObject对象的情况下,需要确保对象的生命周期和父子关系正确地被管理。QObject对象的生命周期受到父子关系的影响,因此需要使用QPointer等智能指针来管理QObject对象的指针。

  • 多线程编程中,需要确保多个线程访问共享对象时不会发生竞态条件。使用智能指针可以避免竞态条件的发生,因为它会自动对对象进行引用计数,确保在所有引用都被释放后才会删除对象

  • 在使用异常处理时,需要确保在函数返回时所有动态分配的对象都被正确地删除。使用智能指针可以确保在函数返回时所有动态分配的对象都被正确地删除,以避免内存泄漏。

  • 防止忘记调用 delete

总之,当我们需要动态分配内存并且需要确保对象的生命周期、所有权、线程安全性等方面的正确管理时,就可以考虑使用智能指针。

2. Qt 中有几种智能指针?

常用的包括 QSharedPointer、QWeakPointer、QScopedPointer和QPointer;

  • QSharedPointer 适用于共享所有权的情况
  • QWeakPointer 适用于不共享所有权但需要访问QSharedPointer所管理的对象的情况
  • QScopedPointer 适用于独占所有权的情况。
  • QPointer 主要用于解决指针的空悬问题,适用于Qt对象之间的引用

    具体介绍如下:

2.1 QSharedPointer 实例

QSharedPointer:强引用,大体相当于C++11 标准中的 shared_ptr, 用于管理动态分配的对象的共享所有权,即多个QSharedPointer对象可以指向同一个对象,并共享该对象的内存管理。

它使用引用计数来追踪对象的使用情况,当最后一个QSharedPointer对象被销毁时,它将自动释放对象的内存。由于使用了引用计数,QSharedPointer能够自动处理指针的生命周期,避免内存泄漏和空悬指针等问题,因此是Qt中最常用的智能指针。

#include <QCoreApplication>
#include <QSharedPointer>
#include <QDebug>

class MyClass
{
public:
    MyClass(int value) : m_value(value) {}
    void setValue(int value) { m_value = value; }
    int getValue() const { return m_value; }
private:
    int m_value;
};

int main()
{
    QSharedPointer<MyClass> pointer1(new MyClass(10));  // 引用计数为1
    {
        QSharedPointer<MyClass> pointer2 = pointer1;    // 引用计数为2
    }                                                   // pointer2销毁,引用计数为1
    qDebug() << "Value:" << pointer1->getValue();       // 输出10
    {
        QSharedPointer<MyClass> pointer3 = pointer1;    // 引用计数为2
        
        pointer1.clear();                               // 引用计数为1
        qDebug() << "Value:" << pointer3->getValue();   // 输出10
        
		QWeakPointer<MyClass> weak(pointer3);
    }                                                   // pointer3销毁,引用计数为0,MyClass对象被自动释放
    return 0;
}

输出结果:
在这里插入图片描述

需要注意的是,QSharedPointer只能管理动态分配的对象的内存。如果我们将其用于指向栈对象或全局对象,那么它就不会自动释放对象的内存,这可能会导致程序崩溃或内存泄漏。

深入了解:C++ 智能指针(shared_ptr/weak_ptr)源码分析

2.2 QSharedPointer 与 QWeakPointer 实例

QWeakPointer:弱引用,大体相当于C++11 标准中的 weak_ptr ,用于在不共享所有权的情况下访问QSharedPointer指向的对象。QWeakPointer指向QSharedPointer所管理的对象,但不会增加对象的引用计数,也不会影响对象的生命周期。当对象被释放时,QWeakPointer会自动被置为空指针,避免了空悬指针的问题。

QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针,它更像是 QSharedPointer 的一个助手,像一个旁观者一样来观测资源的使用情况。

#include <QSharedPointer>
#include <QWeakPointer>
#include <QDebug>

class MyClass
{
public:
    MyClass(int value) : m_value(value) {
        qDebug() << "MyClass constructor called with value" << m_value;
    }
    ~MyClass() {
        qDebug() << "MyClass destructor called with value" << m_value;
    }
    int getValue() const {
        return m_value;
    }

private:
    int m_value;
};

int main()
{
    QSharedPointer<MyClass> shared(new MyClass(20));
    QWeakPointer<MyClass> weak(shared);

    qDebug() << "Shared pointer value:" << shared->getValue();
    qDebug() << "Weak pointer value:" << weak.data()->getValue();

    shared.clear();

    if (weak.isNull()) {
        qDebug() << "Weak pointer is null - object has been deleted"; //执行
    } else {
        qDebug() << "Weak pointer is not null - object still exists";
    }

    return 0;
}

打印结果:

在这里插入图片描述
例中 创建了一个QSharedPointer对象和一个QWeakPointer对象,它们都指向一个MyClass对象。在main函数的结尾处,使用 shared.clear() 来释放 MyClass 对象的所有权。此时,MyClass对象的引用计数为0,将被自动删除,而此时 QWeakPointer 对象 weak 也为null。

2.3 QScopedPointer 实例

QScopedPointer 类似于 C++ 11 中的 unique_ptr 用于管理动态分配的对象的独占所有权,即同一时间只能有一个QScopedPointer指向该对象。

QScopedPointer使用了RAII(资源获取即初始化)技术,当QScopedPointer被销毁时,它将自动释放所管理的对象的内存。QScopedPointer不支持拷贝和赋值操作,这是为了避免在多个指针之间共享所有权的问题。如果需要在多个指针之间转移所有权,应该使用QSharedPointer或QWeakPointer。

如下简单示例:

#include <QScopedPointer>
#include <QDebug>

class MyClass
{
public:
    MyClass(int value) : m_value(value) {
        qDebug() << "MyClass constructor called with value" << m_value;
    }
    ~MyClass() {
        qDebug() << "MyClass destructor called with value" << m_value;
    }
    int getValue() const {
        return m_value;
    }

private:
    int m_value;
};

int main()
{
    QScopedPointer<MyClass> myObject(new MyClass(23));

    qDebug() << "My object value:" << myObject->getValue();

    return 0;
}

结果如下:

在这里插入图片描述

2.4 QPointer 实例

由于QObject对象的生命周期受到父子关系的影响,当父对象被删除时,所有子对象也会被删除。使用 QPointer 可以避免在父对象被删除时,子对象的指针指向已经被删除的对象的问题。

通常情况下,我们在手动delete一个指针的时候,需要再将其置空,要不然会变成一个悬挂的野指针,那么QPointer就是帮忙干这事的,会在对象被销毁时,自动设置为NULL

♦ 场景一:

//
// myqpointer.cpp
//
#include "myqpointer.h"

MyQPointer::MyQPointer(QObject *parent) : QObject(parent)
{
    qDebug() << "MyObject constructor called";
}

MyQPointer::~MyQPointer()
{
    qDebug() << "MyObject destructor called";
}

void MyQPointer::doSomething()
{
    qDebug() << "MyObject is doing something";
}

//
// myqpointer.h
//
#ifndef MYQPOINTER_H
#define MYQPOINTER_H

#include <QObject>
#include <QPointer>
#include <QDebug>

class MyQPointer : public QObject
{
    Q_OBJECT
public:
    explicit MyQPointer(QObject *parent = nullptr);
    ~MyQPointer();
    void doSomething();
};
#endif // MYQPOINTER_H

//
// main.cpp
//
#include <QCoreApplication>
#include "myqpointer.h"

int main(int argc, char *argv[])
{

    QCoreApplication a(argc, argv);

     MyQPointer* myQPointer = new MyQPointer();

    delete myQPointer;
//    myQPointer = nullptr;

    if (myQPointer) {
        qDebug() << "\nMyObject is valid";
        myQPointer->doSomething();
    } else {
        qDebug() << "\nMyObject is null";
    }

    return a.exec();
}

运行结果:
在这里插入图片描述
delete pLabel; 过后如果不手动置空,这里输出将是 “MyObject is valid”。

使用智能指针 QPointer,更改下mian.cc:

//
// main.cpp
//
#include <QCoreApplication>
#include "myqpointer.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QPointer<MyQPointer> myQPointer(new MyQPointer);
    delete myQPointer;
//    myQPointer = nullptr;

    if (myQPointer) {
        qDebug() << "\nMyObject is valid";
        myQPointer->doSomething();
    } else {
        qDebug() << "\nMyObject is null";
    }

    return a.exec();
}

运行结果:

在这里插入图片描述
写法很简单,就是直接将

 MyQPointer* myQPointer = new MyQPointer();

替换成

QPointer<MyQPointer> myQPointer(new MyQPointer);

在main函数的结尾处,我们调用了MyObject对象的deleteLater()函数,将其标记为待删除状态。然后我们再次检查QPointer对象是否为空,此时它应该返回一个空指针

♦ 场景二:

将上述 main.cc 中修改如下:

#include <QCoreApplication>
#include "myqpointer.h"


int main(int argc, char *argv[])
{

    QCoreApplication a(argc, argv);

     MyQPointer* myQPointer1 = new MyQPointer();
     MyQPointer* myQPointer2 = myQPointer1;

    delete myQPointer1;
    myQPointer1 = nullptr;

    if (myQPointer1) {
        qDebug() << "myQPointer1 is valid";
    } else {
        qDebug() << "myQPointer1 is null";
    }
    if (myQPointer2) {
        qDebug() << "myQPointer2 is valid";
    } else {
        qDebug() << "myQPointer2 is null";
    }
    return a.exec();
}

运行结果:

在这里插入图片描述

上面这种情况,myQPointer2 是直接复制myQPointer1,指向同一个地址,这时候delete myQPointer1过后,就不需要再delete myQPointer2 ,否则将会报错,但是 myQPointer2 也需要手动置空,否则变成悬挂的野指针,实际情况中可能经常会忘记置空,甚至将指针delete两次,对于新手来说,这种错误是常犯的。

将上述:

MyQPointer* myQPointer2 = myQPointer1;

修改为:

QPointer<MyQPointer> myQPointer2(myQPointer1);
//QPointer<MyQPointer> myQPointer2 = myQPointer1 ;  //两者都行

运行结果:

在这里插入图片描述

当然最好的方式是,myQPointer1 和 myQPointer2都用智能指针的方式,这样就不用手动置空了。这是 QPointer 最常见的一种使用场景。

♦ 参考:

Qt 中的智能指针

Qt智能指针–QPointer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/494595.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【HarmonyOS】【FAQ】HarmonyOS应用开发相关问题解答(一)

【前提简介】 本文档主要总结HarmonyOS开发过程中可能遇到的一些问题解答&#xff0c;主要围绕HarmonyOS展开&#xff0c;包括但不限于不同API版本HarmonyOS开发、UI组件、DevEco Studio、Gitee示例代码等&#xff0c;并将持续更新哦。 【官方FAQ】 【FAQ】HarmonyOS应用及服…

(十二)地理数据库创建——基本组成项及数据加载

地理数据库创建——基本组成项及数据加载 目录 地理数据库创建——基本组成项及数据加载 1.建立数据库中的基本组成项1.1建立要素数据集1.2建立要素类1.2.1建立简单要素类1.2.2建立关系表 1.3建立关系表 2.向地理数据库加载数据2.1导入数据2.1.1导入Shapefile2.1.2导入dBASE 表…

数据结构:顺序表的增、删,查、改实现

1.概念 顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存 储。在数组上完成数据的增删查改。 2.分类 顺序表一般可以分为&#xff1a; 2.1 静态顺序表&#xff1a;使用定长数组存储元素 这样会造成空间给多了浪费&#x…

ThreadLocal初探

一、ThreadLocal介绍 一、官方介绍 从Java官方文档中的描述&#xff1a;ThreadLocal类用来提供线程内部的局部变量&#xff0c;这种变量在多线程环境下访问&#xff08;通过get和set方法访问&#xff09;时&#xff0c;能够保证各个线程的变量相对独立于其他线程内的变量。Thr…

apple pencil必须要买吗?性价比平替电容笔排行榜

要知道&#xff0c;真正的苹果原装Pencil&#xff0c;价格实在是太贵了&#xff0c;普通的消费者根本买不起。所以&#xff0c;有没有可能出现一种平替的、功能一模一样的、与苹果Pencil一样的电容笔呢&#xff1f;这倒也是。国产的平替笔和苹果Pencil的笔相比&#xff0c;并没…

Wireless-Sensor-Network-master_WSN_无线传感网络(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 近年来&#xff0c;随着对等网络、云计算和网格计算等分布式环境的发展&#xff0c;无线传感器网络&#xff08;WSN&#xff0…

10分钟吃透,python操作mysql数据库的增、删、改、查

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

聊聊汽车OTA测试技术方案

汽车OTA已成为时下热门话题&#xff0c;由于OTA的升级可能会带来一定的风险&#xff0c;针对OTA的测试就尤为重要。本文我们主要通过介绍OTA的发展背景、汽车OTA测试的必要性以及汽车OTA测试内容&#xff0c;为大家分享一套成熟的OTA测试方案。 什么是OTA OTA&#xff08;Over-…

以最大速度将数据迁移至AWS S3存储

数据上云&#xff0c;已经成为企业数据管理发展的必然趋势。 对于企业而言&#xff0c;数据上云“常态化”的趋势&#xff0c;无疑是一个巨大的技术红利。而数据规模爆发式增长的今天&#xff0c;移动和访问云端数据却成为困扰企业的一大难题。不过幸运的是&#xff0c;在对象…

ES6:promise简单学习

一、理解promise Promise将异步执行的程序变成同步执行&#xff0c;所谓的在开发中解决回调嵌套的问题 Promise 是异步编程的一种解决方案 从语法上讲&#xff0c;promise是一个对象 从它可以获取异步操作的消息 从本意上讲&#xff0c;它是承诺&#xff0c;承诺它过一段时间会…

Java入门超详细(内含Java基础 Java高级)

Java基础入门 - 内含Java基础&#xff0c;Java高级 Java 基本介绍Java 学习须知Java 学习文档Java 基础Java 基础语法Java 对象与类Java 基本数据类型Java 变量类型Java 修饰符Java 运算符Java 循环结构Java 条件语句Java switch caseJava 数组Java 日期与时间Java 正则表达式J…

Swift AsyncSequence — 代码实例详解

前言 AsyncSequence 是并发性框架和 SE-298 提案的一部分。它的名字意味着它是一个提供异步、顺序和迭代访问其元素的类型。换句话说&#xff1a;它是我们在 Swift 中熟悉的常规序列的一个异步变体。 就像你不会经常创建你的自定义序列一样&#xff0c;我不期望你经常创建一个…

2022 Jiangsu Collegiate Programming Contest A. PENTA KILL!

题目链接 Sample 1 Input 10 Bin Guigo Grevthar Bin GALA Grevthar GALA TitaN GALA Guigo GALA Aegis GALA Jojo GALA Grevthar Xiaohu Grevthar GALA Aegis Output PENTA KILL! Sample 3 Input 7 GALA Jojo Aegis Ming GALA Grevthar GALA Grevthar GALA Aegis GALA Guigo…

树莓派硬件介绍及配件选择

目录 树莓派Datasheet下载地址&#xff1a; Raspberry 4B 外观图&#xff1a; 技术规格书&#xff1a; 性能介绍&#xff1a; 树莓派配件选用 电源的选用&#xff1a; 树莓派外壳选用&#xff1a; 内存卡/U盘选用 树莓派Datasheet下载地址&#xff1a; Raspberry Pi …

CompletableFutrue异步处理

异步处理 一、线程的实现方式 1. 线程的实现方式 1.1 继承Thread class ThreadDemo01 extends Thread{Overridepublic void run() {System.out.println("当前线程:" Thread.currentThread().getName());} }1.2 实现Runnable接口 class ThreadDemo02 implements …

故障:更新后电脑卡顿

前一天下班的时候关电脑&#xff0c;关机选项变成了“更新并关机”&#xff0c;没多想&#xff0c;我点了。。。。早上上班就发现电脑卡得不行&#xff0c;看了下更新日志&#xff0c;装了一大堆东西&#xff0c;看了下任务管理器&#xff0c;内存直接跑到了90%&#xff0c;这电…

每日学术速递5.2

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.DataComp: In search of the next generation of multimodal datasets 标题&#xff1a;DataComp&#xff1a;寻找下一代多模态数据集 作者&#xff1a;Samir Yitzhak Gadre, Gab…

【JAVA模块六------ 综合案例基础巩固】

JAVA模块六------ 综合案例基础巩固 1 逢7跳过2 数组元素求和3 判断两个数组的内容是否相同4 查找某个数组元素索引5 数组元素反转输出&#xff1a;&#xff08;逆序输出&#xff09;6 评委打分7 随机产生验证码其他&#xff1a;方法抽取&#xff1a; 1 逢7跳过 要求&#xff1…

国产光伏仪器 6581太阳能电池板伏安特性测试仪

6581太阳能电池板伏安特性测试仪主要用于太阳能电池板生产的最终测试&#xff0c;也可以作为层压前测试使用&#xff0c;能大大提高一次封装成品率。该测试仪适合于单晶、多晶、薄膜等多种电池组件&#xff0c;可进行I-V曲线、P-V曲线、短路电流、开路电压、峰值功率等全部参数…

Hive SQL on Flink 构建流批一体引擎

摘要&#xff1a;本文整理自阿里巴巴开发工程师罗宇侠、阿里巴巴开发工程师方盛凯&#xff0c;在 Flink Forward Asia 2022 流批一体专场的分享。本篇内容主要分为五个部分&#xff1a; 1. 构建流批一体引擎的挑战 2. Hive SQL on Flink 3. 流批一体引擎的收益 4. Demo 5. 未来…