QPointer、QScopedPointer、QSharedDataPointer等指针用法总结

本文详细介绍了Qt中的QPointer、QWeakPointer、QScopedPointer和QSharedDataPointer等智能指针类,展示了它们在内存管理、数据共享和性能优化中的应用,以及隐式和显式共享的区别。

目录

2.QWeakPointer、QSharedPointer

3. QScopedPointer

4.QSharedDataPointer、QExplicitlySharedDataPointer

4.1.隐式数据共享

4.2.QSharedData

4.3.QSharedDataPointer 

4.3.1. 描述

4.3.2. 类型成员

4.3.3.成员函数

4.4.QExplicitlySharedDataPointer

4.5 隐式共享示例

4.6 显式共享示例 

4.7. 优化 Qt 容器中的使用性能


       这是一个模板类,是一个警卫、看守指针。QPointer<T>的行为非常类似C++的普通指针T*。和C++的普通指针T*有一点不同的是:当指针指向的对象被销毁时,该指针自动会被清除,即置为nullptr,而C++的普通指针指向的对象被销毁,如果不人工置为nullptr,则不会被自动清除,一般会成为悬垂指针,即野指针。QPointer<T>中的T类型必须是QObject的子类。

当你需要保存一个指向QObject的子类对象(为了便于后文描述,称该对象为A)但该对象被其它对象(为了便于后文描述,称该对象为B)占用时,警卫、看守指针在这种情况下就非常有用。当B删除销毁了A时,使用QPointer<T>可以测试其指向的对象是否有效。如下代码:

  QPointer<QLabel> label = new QLabel;
      label->setText("&Status:");
      ...
      if (label)
          label->show();

当代码执行到第3行时,第1行new出来的QLabel对象在其它地方被delete了,则label就会自动被置为nullptr,从而导致第5行代码不会被执行。如果是C++普通指针,则不会被置为nullptr,代码执行到第4行时会崩溃。

一些在C++普通指针上执行的函数和操作符对 QPointer同样适用,没啥区别。但指针算术操作符是个例外,对 QPointer来说,指针算术操作符+、-、++、--仅仅用在 QObject子类类型的数组上。

你可以从一个T*类型的指针或另外一个同类型的QPointer<T>构造或赋值出一个新的警卫、看守指针QPointer<T>。

你可以通过operator==() 、 operator!=()比较两个警卫、看守指针QPointer<T>是否指向相同的对象或不同的对象;你也可以通过*x 或x->解引用指向的对象。你可以通过isNull()判断指向的对象是否为空。

警卫、看守指针QPointer<T>能自动转换到T*的C++普通指针,所以你可以放心地混合使用T*类型的C++普通指针和QPointer<T>。这意味着下面的代码是可行的:

 QPointer<QLabel> label = new QLabel;

   ............


void fun(QLabel* p)
{
    ............
}

.......

fun(label);

而不必要特意将fun函数的参数定义为QPointer<QLabel>,即下述代码是没多大价值的:

 QPointer<QLabel> label = new QLabel;

   ............


void fun(QPointer<QLabel> p)
{
    ............
}

.......

fun(label);

当你需要长时间保存某个QObject子类的对象时,用QPointer指针。

注意:QPointer封装的指针的类型必须是QObject子类,否则编译器、连接器会报错。

2.QWeakPointer、QSharedPointer

    这两个指针和STL的std::shared_ptr 、std::weak_ptr作用相同,参见《C++ STL 四种智能指针》文章描述。

3. QScopedPointer

  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;
  }

上面的函数有很多退出点,为了删除new出的对象,每个退出点都加入了delete,这使得程序臃肿不堪,可读性、可维护性大大降低。用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);
  }

待用const限定符的对象也可以被QScopedPointer表示,如下:


      const QWidget *const p = new QWidget();
      // is equivalent to:
      const QScopedPointer<const QWidget> p(new QWidget());

      QWidget *const p = new QWidget();
      // is equivalent to:
      const QScopedPointer<QWidget> p(new QWidget());

      const QWidget *p = new QWidget();
      // is equivalent to:
      QScopedPointer<const QWidget> p(new QWidget());
 

用malloc分配的数组对象不能用delete删除,QScopedPointer模板的第二个参数被用来定义一个删除器。下列这些自定义的删除已经存在了:

  • QScopedPointerDeleter 。默认删除器。用delete删除指针。
  • QScopedPointerArrayDeleter。调用delete[]删除new[]出来的数组指针。
  • QScopedPointerPodDeleter。调用free删除malloc出来的指针。
  • QScopedPointerDeleteLater 。通过调用Qt的deleteLater()函数来删除对象。

你可以提供自定义删除器,自定义删除器必须满足:

  • 自定义删除器类必须提供一个静态公有的如下方法。
void cleanup(T *pointer)

如下为自定义的删除器:


  // this struct calls "myCustomDeallocator" to delete the pointer
  struct ScopedPointerCustomDeleter
  {
      static inline void cleanup(MyCustomClass *pointer)
      {
          myCustomDeallocator(pointer);
      }
  };

  // QScopedPointer using a custom deleter:
  QScopedPointer<MyCustomClass, ScopedPointerCustomDeleter> customPointer(new MyCustomClass);

4.QSharedDataPointer、QExplicitlySharedDataPointer

4.1.隐式数据共享

Qt隐式共享的详细描述,请在Qt Assistant 索引查阅,如下:

以下是本人的理解:

Qt 中的许多 C++ 类使用隐式数据共享来最大化资源使用并最小化复制。当作为参数传递时,因为只传递指向数据的指针,并且只有在函数写入时才复制数据(即写时复制),故隐式共享类既安全又高效,

共享类由指向共享数据块的指针组成,该指针包含引用计数和数据。

创建共享对象时,它将引用计数设置为 1。每当新对象引用共享数据时,引用计数就会增加,当对象取消引用共享数据时,引用计数会减少。当引用计数变为零时,共享数据将被删除。

在处理共享对象时,有两种复制对象的方法:深拷贝和浅拷贝。

深拷贝意味着复制一个对象。
浅拷贝是引用拷贝,即只是指向共享数据块的指针。
就内存和 CPU 而言,进行深度复制可能会很昂贵。进行浅拷贝非常快,因为它只涉及设置指针和增加引用计数。

隐式共享对象的对象分配(使用 operator=())是使用浅拷贝实现的。

共享的好处是程序不需要不必要地复制数据,从而减少内存使用和数据复制。对象可以很容易地被赋值,作为函数参数传递,并从函数中返回。


4.2.QSharedData

QSharedData 旨在与 QSharedDataPointer 或 QExplicitlySharedDataPointer 一起使用,以实现自定义的隐式共享显式共享类。QSharedData 提供线程安全的引用计数。

4.3.QSharedDataPointer 

4.3.1. 描述

QSharedDataPointer<T> 用来结合 QSharedData 实现隐式共享数据类,。

4.3.2. 类型成员

QSharedDataPointer::Type共享数据对象的类型。d 指针指向这种类型的对象。

4.3.3.成员函数

构造函数

QSharedDataPointer(QSharedDataPointer<T> &&o)

 移动构造一个 QSharedDataPointer 实例。

QSharedDataPointer(const QSharedDataPointer<T> &o)

将 this 的 d 指针设置为 o 中的 d 指针,并增加共享数据对象的引用计数。

QSharedDataPointer(T *data)

构造一个 QSharedDataPointer,其 d 指针设置为data并增加data的引用计数。

QSharedDataPointer()

构造一个用 nullptr 作为 d 指针初始化的 QSharedDataPointer。

QSharedDataPointer<T> & operator=(QSharedDataPointer<T> &&other)

 将其他移动分配给此 QSharedDataPointer 实例。

QSharedDataPointer<T> & operator=(const QSharedDataPointer<T> &o)

将 this 的 d 指针设置为 o 的 d 指针,并增加共享数据对象的引用计数。this 的旧共享数据对象的引用计数递减。如果旧共享数据对象的引用计数变为0,则删除旧共享数据对象。

~QSharedDataPointer()

减少共享数据对象的引用计数。 如果引用计数变为 0,则删除共享数据对象。 然后将其销毁。

T * clone()

创建并返回当前数据的深拷贝副本。当引用计数大于 1 时,该函数由 detach() 调用以创建新副本。此函数使用运算符 new 并调用类型 T 的拷贝构造函数。

应该为自定义类型声明此函数的模板特化:

    template<> EmployeeData *QSharedDataPointer<EmployeeData>::clone()
    {
        return d->clone();
    }
const T *constData() 

返回一个指向共享数据对象的常量指针。此函数不调用 detach()。

T *data() / T *get()

返回指向共享数据对象的指针。此函数调用 detach()。

const T *data()  / const T *get() 

返回指向共享数据对象的指针。此函数不调用 detach()。

void detach()

如果共享数据对象的引用计数大于 1且需要写时复制,则此函数创建共享数据对象的深度副本,并将 this 的 d 指针设置为副本。则该函数由 QSharedDataPointer 的非常量成员函数自动调用。不需要自己调用它。关于该函数的理解,请参考《Qt隐式共享detach函数的理解

void reset(T *ptr = nullptr)

将 this 的 d 指针设置为 ptr,如果 ptr 不是 nullptr,则增加 ptr 的引用计数。旧共享数据对象的引用计数递减,如果引用计数达到0,则删除该对象。

T *take()

返回指向共享对象的指针(即QSharedDataPointer指向的对象的地址),并将自身(即QSharedDataPointer对象自己)其重置为 nullptr。(即将即QSharedDataPointer对象自己  的 d 指针设置为 nullptr)

返回对象的引用计数不会递减。此函数可以与构造函数一起使用,该构造函数采用 QAdoptSharedDataTag 标记对象来传输共享数据对象,而无需干预原子操作。


4.4.QExplicitlySharedDataPointer

QExplicitlySharedDataPointer用来实现显示共享数据类。显式共享需要手动调用 detach() 来进行数据的拷贝,除此之外这个类的行为和QSharedDataPointer 一致。

ExplicitlySharedDataPointer 的 d 指针总是指向共享数据对象的内部指针。

4.5 隐式共享示例

假设要隐式共享一个 员工(Employee) 类:

在头文件中,定义两个类 Employee 和 EmployeeData。

//员工数据
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; 
    }
    Employee(int id, const QString &name) 
    {
        d = new EmployeeData;
        setId(id);
        setName(name);
    }
    Employee(const Employee &other)
          : d (other.d)
    {
    }
    void setId(int id) 
    {
        d->id = id; 
    }
    void setName(const QString &name) 
    {
        d->name = name; 
    }
 
    int id() const { return d->id; }
    QString name() const { return d->name; }
 
  private:
    QSharedDataPointer<EmployeeData> d;//D指针指向员工信息
};

在 Employee 类中,注意单个数据成员,一个 QSharedDataPointer<EmployeeData> 类型的 d 指针。所有对 EmployeeData 的访问都必须经过 d 指针的 operator->()。对于写访问,operator->() 将自动调用 detach(),如果共享数据对象的引用计数大于 1,它会创建共享数据对象的副本。这确保写入一个 Employee 对象不会影响任何 共享相同 EmployeeData 对象的其他 Employee 对象。

EmployeeData 类继承了 QSharedData,它提供了内部引用计数器。EmployeeData 有一个默认构造函数、一个复制构造函数和一个析构函数。通常,在隐式共享类的数据类中只需要这些的简单实现。

请注意,不需要为 Employee 类实现复制构造函数或赋值运算符,因为 C++ 编译器提供的复制构造函数和赋值运算符将逐个成员进行所需的成员浅拷贝。唯一要复制的成员是 d 指针,它是一个 QSharedDataPointer,其 operator=() 只会增加共享 EmployeeData 对象的引用计数。
 

#include "employee.h"
 
int main()
{
    Employee e1(1001, "张三");
    Employee e2 = e1;
    e1.setName("李四");
}

在创建第二个员工 e2 并将 e1 分配给它之后,e1 和 e2 都引用了员工 1001 的 张三。两个 Employee 对象都指向 EmployeeData 的同一个实例,它的引用计数为 2。然后 e1.setName("李四") 被调用以更改员工姓名,但由于引用计数大于 1,因此在更改姓名之前执行写入时复制。现在 e1 和 e2 指向不同的 EmployeeData 对象。它们有不同的名称,但都有 ID 1001。
 

4.6 显式共享示例 

如果将 Employee 类中的 d 指针声明为 QExplicitlySharedDataPointer<EmployeeData>,则使用显式共享并且不会自动执行写操作时的复制(即,在非常量函数中不调用 detach())。在这种情况下,在 e1.setName("Hans Holbein") 之后,员工的姓名已更改,但 e1 和 e2 仍然引用相同的 EmployeeData 实例,因此只有一个员工 ID 为 1001。

e1和e2引用相同的示例。

调用detach()复制:

4.7. 优化 Qt 容器中的使用性能

如果自定义的隐式共享类类似于上面的 Employee 类并使用 QSharedDataPointer 或 QExplicitlySharedDataPointer 作为唯一成员,则应考虑使用 Q_DECLARE_TYPEINFO() 宏将隐式共享类标记为可移动类型。这可以在使用 Qt 的容器类时提高性能和内存效率。

参考链接:https://blog.youkuaiyun.com/kenfan1647/article/details/120284812

Qt智能指针--QScopedPointer ⽂章⽬录 概述 前⼀篇⽂章我们详细的介绍了的⽤法,那么,这⾥继续总结Qt的另⼀个智能指针QScopedPointer的⽤法。 QScopedPointer和C++中的智能指针std::unique_ptr其概念是⼀样的,它包装了new操作符在堆上分配的动态对象,能够保证动态创建 的对象在任何时候都可以被正确地删除。但它有更严格的所有权,并且不能转让,⼀旦获取了对象的管理权,你就⽆法再从它那⾥取回来。 也就是说,只要出了作⽤域,指针就会被⾃动删除,因为它的拷贝构造和赋值操作都是私有的,与QObject及其派⽣类风格相同。 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; } 上⾯的写法,稍有不慎就会导致内存泄露,但是如果使⽤智能指针,就会变得很简单了: void myFunction(bool useSubClass) { QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass); QScopedPointer<QIODevice> device(handsOverOwnership()); if (m_value > 3) return; process(device); } 注意:因为拷贝构造和赋值操作私有的,所以不能⽤作容器的元素。 const 限制 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()); 考虑⼀种情况 上⾯说到,使⽤QScopedPointer智能指针动态创建的对象,⼀旦出了作⽤域就会 被⾃动释放并置空,那么如果需要函数返回值怎么办 呢? ⽐如下⾯这种情况: QLabel * createLabel() { QScopedPointer<QLabel> pLabel(new QLabel()); // return pLabel.data(); //invalid return pLabel.take(); //valid } int main(int argc, char *argv[]) { QApplication a(argc, argv); QScopedPointer<QLabel> p1(createLabel()); p1->setText("hello"); p1->show(); return a.exec(); } 注意,我们在createLabel()函数中创建label对象并返回时,不能使⽤data(),⽽要使⽤take(); 因为 T *QScopedPointer::data() const返回指向对象的常量指针QScopedPointer仍拥有对象所有权。 所以通过data()返回过后就被 ⾃动删除了,从⽽导致mian函数中的p1变成了野指针,程序崩溃。 ⽽使⽤T *QScopedPointer::take()也是返回对象指针,但QScopedPointer不再拥有对象所有权,⽽是转移到调⽤这个函数的caller,同 时QScopePointer对象指针置为NULL。 另外还有⼀个函数要注意。 void QScopedPointer::reset(T *other = Q_NULLPTR):delete⽬前指向的对象,调⽤其析构函数,将指针指向另⼀个对象other,所有 权转移到
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值