Qt 中的智能指針
上一篇博客中介紹了 C++11 標准中的提供的智能指針。在 Qt 中也提供了類似的替代功能,並且比 C++11 標准中提供的功能還要強大,所以如果我們使用 Qt 作為基礎庫,那么就沒有必要使用C++11 的智能指針。
Qt 的智能指針包括:
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 工作,像一個旁觀者一樣來觀測資源的使用情況。