Qt 中的智能指针

Qt 中的智能指针

上一篇博客中介绍了 C++11 标准中的提供的智能指针。在 Qt 中也提供了类似的替代功能,并且比 C++11 标准中提供的功能还要强大,所以如果我们使用 Qt 作为基础库,那么就没有必要使用C++11 的智能指针。

Qt 的智能指针包括:

  • QSharedPointer
  • QScopedPointer
  • QScopedArrayPointer
  • QWeakPointer
  • QPointer
  • QSharedDataPointer

QSharedPointer

QSharedPointer 大体相当于C++11 标准中的 shared_ptr。是在 Qt 4.5 中引入的,所以只要我们的 Qt 版本大于 Qt 4.5 就可以使用这个类。

要使用这个智能指针类,首先要包含对应的头文件:

#include <QSharedPointer>

QSharedPointer 内部维持着对拥有的内存资源的引用计数。比如有 5个 QSharedPointer 拥有同一个内存资源,那么这个引用计数就是 5。这时如果一个 QSharedPointer 离开了它的作用域,那么就还剩下 4 个 QSharedPointer 拥有这个内存资源,引用计数就变为了 4。 当引用计数下降到 0 时,这个内存资源就被释放了。

QSharedPointer 的构造函数有如下这几种。

QSharedPointer();
QSharedPointer(X *ptr);
QSharedPointer(X *ptr, Deleter deleter);
QSharedPointer(std::nullptr_t);
QSharedPointer(std::nullptr_t, Deleter d);
QSharedPointer(const QSharedPointer<T> &other);
QSharedPointer(const QWeakPointer<T> &other);

因此我们可以:
- 构造一个空的 QSharedPointer
- 通过一个普通指针来构造一个 QSharedPointer
- 用另一个 QSharedPointer 来构造一个 QSharedPointer
- 用一个 QWeakPointer 来构造一个 QSharedPointer

并且,我们可以指定 Deleter。因此 QSharedPointer 也可以用于指向 new[] 分配的内存。

QSharedPointer 是线程安全的,因此即使有多个线程同时修改 QSharedPointer 对象也不需要加锁。这里要特别说明一下,虽然 QSharedPointer 是线程安全的,但是 QSharedPointer 指向的内存区域可不一定是线程安全的。所以多个线程同时修改 QSharedPointer 指向的数据时还要应该考虑加锁的。

下面是个 QSharedPointer 的例子,演示了如何自定义 Deleter。

  static void doDeleteLater(MyObject *obj)
  {
      obj->deleteLater();
  }

  void otherFunction()
  {
      QSharedPointer<MyObject> obj = QSharedPointer<MyObject>(new MyObject, doDeleteLater);

      // continue using obj
      obj.clear();    // calls obj->deleteLater();
  }

QSharedPointer 使用起来就像是普通的指针。唯一让我不满意的地方就是 QSharedPointer 不支持指向一个数组。

QScopedPointer

QScopedPointer 类似于 C++ 11 中的 unique_ptr。当我们的内存数据只在一处被使用,用完就可以安全的释放时就可以使用 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);
  }

QScopedArrayPointer

如果我们指向的内存数据是一个数组,这时可以用 QScopedArrayPointer。QScopedArrayPointer 与 QScopedPointer 类似,用于简单的场景。

例如下面这个例子:

void foo()
  {
      QScopedArrayPointer<int> i(new int[10]);
      i[2] = 42;
      ...
      return; // our integer array is now deleted using delete[]
  }

QPointer

QPointer 与其他的智能指针有很大的不同。其他的智能指针都是为了自动释放内存资源而设计的。 QPointer 智能用于指向 QObject 及派生类的对象。当一个 QObject 或派生类对象被删除后,QPointer 能自动把其内部的指针设为 0。这样我们在使用这个 QPointer 之前就可以判断一下是否有效了。

为什么非要是 QObject 或派生类呢,因为 QObject 可以构成一个对象树,当这颗对象树的顶层对象被删除时,它的子对象自动的被删除。所以一个 QObject 对象是否还存在,有时并不是那么的明显。有了 QPointer 我们在使用一个对象之前,至少可以判断一下。

要特别注意的是,当一个 QPointer 对象超出作用域时,并不会删除它指向的内存对象。这和其他的智能指针是不同的。

下面给一个简单的例子:

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

QSharedDataPointer

QSharedDataPointer 这个类是帮我们实现数据的隐式共享的。我们知道 Qt 中大量的采用了隐式共享和写时拷贝技术。比如下面这个例子:

QString str1 = "abcdefg";
QString str2 = str1;
QString str2[2] = 'X';

第二行执行完后,str2 和 str1 指向的同一片内存数据。当第三句执行时,Qt 会为 str2 的内部数据重新分配内存。这样做的好处是可以有效的减少大片数据拷贝的次数,提高程序的运行效率。

Qt 中隐式共享和写时拷贝就是利用 QSharedDataPointer 和 QSharedData 这两个类来实现的。

比如我们有个类叫做 Employee,里面有些数据希望能够利用隐式共享和写时拷贝技术。那么我们可以把需要隐式共享的数据封装到另一个类中,比如叫做 EmployeeData,这个类要继承自 QSharedData。

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

这个例子中,id 和 name 就是我们要隐式共享和写时拷贝的数据。那么 Employee 类需要这么来实现。

class Employee
{
public:
    Employee() { d = new EmployeeData; }
    Employee(const Employee &other)
            : d (other.d)
      {
      }
    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;
};

我们对 Employee 中数据的访问都要通过 d。那么当需要修改 id 或 name 时,QSharedDataPointer 类自动的调用 detach() 方法来完成数据的拷贝。d (other.d) 则完成了隐式共享的功能。

看到了吧,就这么简单。如果这些功能都要自己实现的话,代码量可是不小的。

于这个类有关的还有个 QExplicitlySharedDataPointer 这个类用到的机会更少,所以就不多做介绍了,请大家自己看 Qt 的帮助文档。

QWeakPointer

最后来简单介绍一下 QWeakPointer。这个类用到的机会不多。(说实话我不太会用这个类,下面的介绍都是网上抄的.)

QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针,它更像是 QSharedPointer 的一个助手(因为它不具有普通指针的行为,没有重载operator*和->)。它的最大作用在于协助 QSharedPointer 工作,像一个旁观者一样来观测资源的使用情况。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值