首页 > 其他分享 >QT 智能指针 QPointer QScopedPointer QSharedPointer QWeakPointer QSharedDataPointer 隐式共享 显示共享

QT 智能指针 QPointer QScopedPointer QSharedPointer QWeakPointer QSharedDataPointer 隐式共享 显示共享

时间:2024-03-22 15:12:55浏览次数:26  
标签:QPointer QT QSharedPointer 对象 QScopedPointer 共享 指针

QPointer

QPointer 是一种受保护的指针,当其引用的对象被销毁时,它会被自动清除(但是,销毁引用对象还是必须手动delete)。QPointer所指向的对象必须是QObject或其派生类对象。

当多个指针指向同一个 Object 对象时,引用的对象可能被释放掉,这时使用 QPointer 就可以安全的测试引用对象是否有效,防止发生指针空悬。

注意:Qt5 之前,QPointer 指向一个 QWidget 对象(或子类对象)时,QPointer 由 QWidget 的析构函数清除,Qt5 之后 由 QObject 的析构函数清除。在析构函数销毁被跟踪 QWidget 的子项之前,任何跟踪 QWidget 的 QPointers 都不会被清除。

QPointer 提供的函数和运算符与普通指针的函数和运算符相同,但算术运算符+、-、 ++ 和 --除外(它们通常仅用于对象数组)。

创建 QPointer 指针,可以使用构造函数、用 T * 赋值或相同类型的其他 QPointer 。QPointer 比较可以使用 == 和 !=,或使用 isNull() 进行测试。可以使用 nullptr 或 *xx->member 取消引用。

QPointer 和普通指针可以混用,QPointer会自动转换为指针*。可以把 QPointer 对象传递给需要 QWidget * 参数的函数。因此,声明函数时没有必要用 QPointer 作为参数,只需使用普通指针即可。

#include <QCoreApplication>
#include <QTimer>
#include <QDebug>
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QTimer *timer = new QTimer;
    // QPointer<QTimer> timer = new QTimer;
    delete timer;
    // 不使用QPointer时需要设置timer为NULL
    // timer=NULL;
    if(timer)
    {
        qInfo()<<"timer is not null";
    }
    else
    {
        qInfo()<<"timer is null";
    }
    return a.exec();
}

不使用QPointer时,输出“Label is not null”,原因是,delete后未置空,易造成指针悬空。使用 QPointer 时,输出"timer is null"。
在QPointer中 .是指针的属性,-> 是对象的属性。

QScopedPointer

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

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

手动管理堆中分配的对象非常困难而且容易出错,容易导致内存泄露。QScopedPointer是一个简化内存管理的工具类,它通过将堆分配的空间赋值给基于栈的内存,通常称为RAII(resource acquisition is initialization)。
QScopedPointer保证指向的对象在当前范围消失时自动删除。
下面的函数分配了堆内存,使用后手动删除。

void myFunction(bool useSubClass)
 {
     MyClass *p = useSubClass ? new MyClass() : new MySubClass;
     QIODevice *device = handsOverOwnership();

     if (m_value > 3) {
         delete p;
         delete device;
         return;
     }

     try {
         process(device);
     }
     catch (...) {
         delete p;
         delete device;
         throw;
     }

     delete p;
     delete device;
 }

使用QScopedPointer可以简化上面的代码:

void myFunction(bool useSubClass)
 {
     // assuming that MyClass has a virtual destructor
     QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
     QScopedPointer<QIODevice> device(handsOverOwnership());

     if (m_value > 3)
         return;

     process(device);
 }

编译器为QScopedPointer生成的代码与手动编写的代码相同。delete代码是QScopedPointer的选项之一。QScopedPointer没有赋值构造函数或赋值操作,以便清楚的表达所有权和生命周期。
C++ 中const修饰的指针也可以使用QScopedPointer来表示。

    const QWidget *const p = new QWidget();
    // 等价于
    const QScopedPointer<const QWidget> p(new QWidget());

    QWidget *const p = new QWidget();
    // 等价于
    const QScopedPointer<QWidget> p(new QWidget());

    const QWidget *p = new QWidget();
    // 等价于
    QScopedPointer<const QWidget> p(new QWidget());

自定义清理处理器:
使用malloc分配的数组指针不能使用delete删除,QScopedPointer的第二个模板参数可以用于自定义清理处理器。QT提供了以下自定义清理处理器:

  • QScopedPointerDeleter 默认处理器,使用delete删除指针。
  • QScopedPointerArrayDeleter 使用delete []删除。用于处理使用new [] 创建的指针。
  • QScopedPointerPodDeleter 使用free()删除指针。用于处理使用malloc()创建的指针。
  • QScopedPointerDeleteLater 使用deleteLater()删除指针。用于处理QEventLoop中使用的QObject指针。
    自定义清理处理器中必须提供一个public的静态函数:public static function void cleanup(T *pointer)
 // QScopedPointer 使用 delete[] 删除数据
 QScopedPointer<int, QScopedPointerArrayDeleter<int> > arrayPointer(new int[42]);

 // QScopedPointer 使用 free()释放内存
 QScopedPointer<int, QScopedPointerPodDeleter> podPointer(reinterpret_cast<int *>(malloc(42)));

 // 该 struct 调用 "myCustomDeallocator" 删除指针
 struct ScopedPointerCustomDeleter
 {
    static inline void cleanup(MyCustomClass *pointer)
    {
        myCustomDeallocator(pointer);
    }
 };

 // QScopedPointer 使用自定义清理处理器:
 QScopedPointer<MyCustomClass, ScopedPointerCustomDeleter> customPointer(new MyCustomClass);

QScopedPointer可以使用前置声明的类,但是要保证QScopedPointer需要清理的时候前置声明的类的析构函数可用。否则,编译器将输出析构函数不可用的警告。

 class MyPrivateClass; // 前置声明 MyPrivateClass

class MyClass
{
private:
    QScopedPointer<MyPrivateClass> privatePtr; // QScopedPointer 使用前置声明类

public:
    MyClass(); // OK
    inline ~MyClass() {} // VIOLATION - 析构函数不能使用inline修饰

private:
    Q_DISABLE_COPY(MyClass) // OK - 设置拷贝构造函数和赋值操作不可用,编译器不会自动生成构造函数和赋值操作
};

成员函数

QScopedPointer::QScopedPointer(T *p = nullptr)

构造 QScopedPointer 实例,并指向p

QScopedPointer::~QScopedPointer()

销毁QScopedPointer对象。删除它指向的对象

T *QScopedPointer::data() const

返回QScopedPointer指向对象的值。QScopedPointer仍然指向该对象

T *QScopedPointer::get() const

与 data()功能相同

bool QScopedPointer::isNull() const

QScopedPointer指向的对象为nullptr时返回true

void QScopedPointer::reset(T *other = nullptr)

删除已指向的对象并指向新对象other

QScopedArrayPointer

QScopedArrayPointer 与 QScopedPointer 类似,但是删除指针时使用的时 delete[] 操作。QScopedArrayPointer 存储的指针指向动态分配的数组对象。如果我们指向的内存数据是一个数组,这时可以用 QScopedArrayPointer。例如:

 void foo()
 {
     QScopedArrayPointer<int> i(new int[10]);
     i[2] = 42;
     ...
     return; // 此时我们定义的 integer 数组会使用 delete[] 自动删除
 }

QSharedPointer

QSharedPointer 相当于C++11 标准中的 shared_ptr, 用于管理动态分配的对象的共享所有权,即多个 QSharedPointer 对象可以指向同一个对象,并共享该对象的内存管理。它使用引用计数来追踪对象的使用情况,当最后一个 QSharedPointer 对象被销毁时,它将自动删除它所持有的指针。由于使用了引用计数,QSharedPointer 能够自动处理指针的生命周期,避免内存泄漏和空悬指针等问题,因此是Qt中最常用的智能指针。

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

创建 QSharedPointer 对象可以用普通指针、另一个 QSharedPointer 对象,也可以通过将 QWeakPointer 对象提升为强引用来创建。

线程安全

QSharedPointer 和 QWeekPointer 是可重入类,如果不进行同步,多个线程无法同时访问指定的 QSharedPointer 对象或 QWeakPointer 对象。

多个 QSharedPointer 和 QWeekPointer 指向同一个对象的情况下,多个线程可以安全的访问这些 QSharedPointer 和 QWeekPointer对象。QSharedPointer 采用的引用计数机制是原子操作,不需要手动同步。但是要注意,QSharedPointer 和 QWeekPointer 所指向的对象不一定是线程安全的,需要采用线程安全和重入规则来保证 QSharedPointer 和 QWeekPointer 所指向对象的安全。

其他指针类

Qt还提供了另外两个指针包装类: QPointer 和 QSharedDataPointer。它们彼此不兼容,因为每个都有其非常不同的用例。

QSharedPointer 通过外部引用计数(即放置在对象外部的引用计数器)持有共享指针,指针值在 QSharedPointer 和 QWeekPointer 的所有实例之间共享。但是,指针指向的对象不应被视为共享的:都是同一个对象。QSharedPointer 不提供detach(隐式共享)或拷贝所引用对象的方法。

QSharedDataPointer 通过基类 QSharedData 内的引用计数持有共享数据的指针(共享数据派生自 QSharedData 类)。QSharedDataPointer 可以根据访问类型对受保护数据执行detach(隐式共享):如果不是读访问,则以原子方式创建一个副本以完成操作。

QExplicitlySharedDataPointer 是 QSharedDataPointer 的一个变量,QSharedDataPointer只有在 QExplicitlySharedDataPointer::detach() 时才会执行detach。

QScopedPointer 专为堆内分配、删除对象设计,它持有指向堆分配对象的指针,并在其析构函数中删除对象。 QScopedPointer 是轻量级的,它不使用额外的结构或引用计数。

QPointer 用于持有 QObject 派生对象的指针,但是一种弱引用。QWeakPointer 具有相同的功能,但不推荐使用该功能。

可选指针跟踪
编译调试时可以开启 QSharedPointer 的指针跟踪功能。启用后,QSharedPointer 会在全局集合中注册它跟踪的所有指针。这样就可以捕获错误(例如:将同一指针分配给两个 QSharedPointer 对象)。

开启指针跟踪功能需要在 include QSharedPointer 前定义宏QT_SHAREDPOINTER_TRACK_POINTERS。

即使没有启用指针跟踪功能进行代码编译,使用 QSharedPointer 跟踪指针也是安全的。如果编译的代码没有开启指针跟踪功能,QSharedPointer 会从跟踪器中删除指针。

注意,指针跟踪功能对多重继承或虚拟继承有限制(此时两个不同的指针可以引用同一对象)。在这种情况下,如果指针被强制转换为不同的类型并且其值发生更改,则 QSharedPointer 的指针跟踪机制可能无法检测到被跟踪的对象是否为同一对象。

QWeakPointer

QWeakPointer 是对指针的弱引用,相当于C++11 标准中的 weak_ptr。QWeakPointer 不影响指针引用计数,可以用于验证指针是否在已另一个上下文中被删除。QWeakPointer对象的创建只能通过QSharedPointer赋值。QWeakPointer 用于追踪指针,但并不代表指针本身,它不保证指针对象的有效性,也不提供转换操作。如果要访问 QWeakPointer 追踪的指针,需要向将其提升为 QSharedPointer 并验证该对象是否为 null,若对象不为 null,则可以使用该指针。QWeakPointer::toStrongRef()用于将 QWeakPointer 转换为 QSharedPointer。
QWeakPointer 指向 QSharedPointer 所管理的对象,但不会增加对象的引用计数,也不会影响对象的生命周期。当对象被释放时,QWeakPointer会自动被置为空指针,避免了空悬指针的问题。

#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();
    // 此时,MyClass对象的引用计数为0,将被自动删除,而此时 QWeakPointer 对象 weak 也为null。

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

    return 0;
}

QSharedDataPointer

QSharedDataPointer 用于简化隐式共享类的实现。QSharedDataPointer 实现了线程安全的引用计数,保证了向可重入类添加 QSharedDataPointers 时不破坏的的可重入性。Qt 中很多类都采用了隐式共享来提升指针的访问速度和内存使用效率。Qt 的容器类都使用了隐式共享,如 QList 、QVarLengthArray 、QStack 、QQueue、 QSet、 QMap、 QMultiMap、 QHash、 QMultiHash 。
例如,实现 Employee 类的隐式共享,需要以下步骤:

  1. 定义 Employee 类,并声明一个 QSharedDataPointer 成员变量;
  2. 定义一个 EmployeeData 并继承 QSharedData,将 Employee 需要的成员变量在 EmployeeData 中进行声明。
#include <QSharedData>
#include <QString>

// EmployeeData 继承自 QSharedData,QSharedData为其提供了一个引用计数器
// EmployeeData 要实现共享必须提供默认的构造函数、拷贝构造函数和析构函数
// 如果要对 Employee 类的使用者隐藏数据,应该把 EmployeeData 类声明到独立的 .h 文件中
class EmployeeData : public QSharedData
{
  public:
    EmployeeData() : id(-1) { }
    // 拷贝构造函数
    EmployeeData(const EmployeeData &other)
        : QSharedData(other), id(other.id), name(other.name) { }
    ~EmployeeData() { }

    // 共享数据
    int id;
    QString name;
};

class Employee
{
  public:
    Employee() { d = new EmployeeData; }
    // 该构造函数调用了 setId(id) 和 setName(name), 2次修改共享数据,但是并不会触发 copy on write
    Employee(int id, const QString &name) {
        d = new EmployeeData;
        setId(id); // 此时 EmployeeData 的引用计数为 1
        setName(name); // 引用计数为 1 ,不会 copy 共享数据
    }
    // 这个拷贝构造函数可以不用定义,编译器会自动生成拷贝构造函数
    // 拷贝构造函数会对成员变量 d 进行赋值,赋值操作 =() 会增加共享数据的引用计数
    Employee(const Employee &other)
          : d (other.d)
    {
    }
    // 修改数据时,操作符 ->() 会自动调用 detach() ,如果共享数据的引用计数大于1,会自动copy一份数据用于修改。
    // 保证一个 Employee 对象对共享数据的修改不会影响其它Employee对象引用的共享数据
    void setId(int id) { d->id = id; }
    void setName(const QString &name) { d->name = name; }

    // 不修改 EmployeeData ,操作符 -> 不会触发detach()
    int id() const { return d->id; }
    QString name() const { return d->name; }

  private:
    // Employee 只用一个成员变量,其它数据在 EmployeeData 中定义
    QSharedDataPointer<EmployeeData> d;
};

在上面的场景中,Employee 对象拷贝、赋值或作为参数传递时,QSharedDataPointer 自动增加引用计数。Employee 对象删除或不在当前范围时, QSharedDataPointer 自动减少引用计数。当引用计数为 0 时,EmployeeData 对象自动被删除。当 Employee 的成员函数对成员变量 d 进行修改时,QSharedDataPointer 自动调用 detach() 操作,并为 Employee 对象拷贝出一份数据,保证 Employee 对象对数据的修改不会影响其它对象。如果 Employee 有多个成员函数都会修改成员变量 d,detach() 会被调用多次,但是只有首次调用 detach() 时才会拷贝共享数据,因为第一次调用 detach() 后成员变量 d 指向新拷贝的数据,它的引用计数为 1。

隐式共享需要注意的问题:
看下面的代码,如果 e1 和 e2 都代表 id 为 1001 的数据,那么将出现 1001 有 2 个不同 name 的问题,这在数据一致性上可能会有问题。这种问题需要使用显示共享来解决,即在 Employee 类中声明为显示共享 QExplicitlySharedDataPointer d; 显示共享不会自动执行 copy on write 操作。

#include "employee.h"

int main()
{
    Employee e1(1001, "Albrecht Durer");
    // e1 和 e2 指向同一个共享数据(1001, "Albrecht Durer")
    Employee e2 = e1;
    // setName 后成员变量 e1 将指向另一个数据(1001, "Hans Holbein")
    e1.setName("Hans Holbein");
}

可以考虑使用宏Q_DECLARE_TYPEINFO()将隐式共享类声明为 movable 类型,例如上面的 Employee 类。

隐式共享的 iterator 问题:当使用 iterator 遍历容器时,不能进行容器拷贝。例如:

QList<int> a, b;
// 构造一个用 0 初始化的 list
a.resize(100000); 

QList<int>::iterator i = a.begin();
// 下面是 iterator i 的错误使用方式

b = a;

// 与 STL 容器不同的是,如果此时执行 *i = 4 会发生隐式拷贝

a[0] = 5;
// 此时 a 已经从共享数据中分离出来,虽然 i 是 容器 a 的遍历器,但实际上是 b 的遍历器,此时 (*i) == 0

b.clear(); 
// 此时遍历器 i 已经完全无效了

int j = *i; 
// 此时会出现 i 未定义的情况

// 如果使用的是 STL 的容器类 std::list<T> 不会发生上述情况,此时 (*i) == 5

QExplicitlySharedDataPointer

QExplicitlySharedDataPointer 用于简化显式共享类的实现。QExplicitlySharedDataPointer 实现了线程安全的引用计数,保证了向可重入类添加 QSharedDataPointers 时不破坏的的可重入性。
QExplicitlySharedDataPointer 与 QSharedDataPointer 类似,惟一不同的是 QExplicitlySharedDataPointer 的成员函数在出现写操作时不会自动 copy 共享数据。QExplicitlySharedDataPointer 的 detach() 方法只能手动调用。QExplicitlySharedDataPointers 自动进行引用计数并在引用计数变为 0 时删除共享数据。

参考文章:
Qt 智能指针介绍: QSharedPointer、QWeakPointer 、QScopedPointer 、QPointer(附实例)
Qt 中的智能指针
【图解】Qt中的智能指针
Qt 智能指针学习(7种QT的特有指针)

标签:QPointer,QT,QSharedPointer,对象,QScopedPointer,共享,指针
From: https://www.cnblogs.com/ITnoteforlsy/p/18089527

相关文章

  • 【包远程安装运行】SpringBoot+Mysql实现的共享厨房平台+演示视频+开发文档(论文模板)
    今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的共享厨房平台系统,该系统可以实现线上提前预约,线下使用。利用支付宝沙箱来作为支付方式,使该系统更切合实际的表现出实体店线下共享厨房的流程。该系统分为前台和后台。主要实现了除脚手架功能以外下......
  • C++ Qt开发:QProcess进程管理模块
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QProcess组件实现针对进程的控制管理等。当你在使用Qt进行跨平台应用程序开发时,经常需要......
  • 【Qt5】QVariant
    2024年3月22日,周五下午什么是QVariantQVariant是Qt框架中用于处理各种数据类型的通用类。它可以存储几乎任何类型的数据,并且能够在不同的Qt类之间进行类型转换。QVariant在Qt中被广泛用于处理不同的数据类型,包括基本数据类型(如整数、浮点数、布尔值等)、字符串、自定......
  • Centos7交叉编译QT5.11.1+AArch64架构
    一、准备工作1、更新系统软件,目的是使用新版本的gcc等,还要保证磁盘空间足够1yumupdate2yuminstallgccgcc-c++3yuminstallmesa-libGL-develmesa-libGLU-develfreeglut-devel2、到linaro下载编译链工具安装aarch64(arm64)编译链工具。国产CPU,如飞腾、华为麒麟等......
  • Qt信号 lamda 表达式使用
    对于Qt信号的绑定,有几种方式:1.标准的SIGNAL和SLOT的绑定方式; 例如:connect(qApp,SIGNAL(focusChanged(QWidget*,QWidget*)),this,SLOT(focusChanged(QWidget*,QWidget*)));//qApp为发送者,this类为接收者;focusChanged为发送者发送的信号,fo......
  • QT6 android下配置文件打包到APK
    一.遇到问题   用QT做android开发,生成的apk文件默认不包含程序配置文件和png文件,导致程序在android上无法读取必要的参数和logo,本文主要解决这个问题,并详细讲述QTandroid的apk程序打包流程。二.APK打包流程1.如何生成APK  当我们的工程的构建套件选择Android编译器......
  • qt限制程序只能运行一个
    1#include<QtWidgets/QApplication>2#include<QSharedMemory>3#include<QMessageBox>45intAssumeSingleInstance(constQString&key)6{7staticQSharedMemoryshm(key);8if(shm.create(100)==false)9{1......
  • QToolTip字体大小导致显示不全
    QToolTip通过qss设置字体大小后,有时候显示不全,有时候可以显示全,原因不明。显示不全的时候,看着像是按照一个较小的字体大小显示显示不全的情况: 显示全的情况: qss如下QToolTip{border:0px;border-radius:4px;font-size:15ptcolor:rgb(255,255,255);backgroun......
  • QT增加线程函数步骤流程
    在使用线程的时候,不仅要关注线程开启的时机,同时还要关注线程安全退出,这样才能保证程序的健壮性,如果线程开启的较多,且开启关闭比较频繁,建议使用线程池来处理。开启线程有三种方式:第一种C++的开线程方式;第二种自定义一个继承自QObject的类,把要处理的事情放在这个类中实现,然后再ne......
  • 如何设置打印机共享
    原文链接:https://baijiahao.baidu.com/s?id=1735863807636399903&wfr=spider&for=pc1、首先将打印机连接到一台主电脑,然后安装好打印机驱动,进入到【控制面板】-【设备和打印机】,下方就会出现这台打印机的图标了。2、鼠标右击这个打印机图标,选择【打印机属性】,进入后点击【共享......