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 工作,像一个旁观者一样来观测资源的使用情况。