Qt記憶體管理(三) Qt的智慧指標
智慧指標則可以在退出作用域時(不管是正常流程離開或是因異常離開)總呼叫delete來析構在堆上動態分配的物件。Qt常用的智慧指標有QPointer,QScopedPointer,QSharedPointer。
關於這幾個智慧指標,網上的部落格基本不是翻譯Qt文件,就是翻譯老外的部落格,比較失望。
QPointer
QPointer屬於Qt物件模型的特性,本質是一個模板類,它為QObje提供了guarded pointer
。當其指向的物件(必須是QObject及其派生類)被銷燬時,它會被自動置NULL,原理是其物件析構時會執行QObject的解構函式,進而執行QObjectPrivate::clearGuards(this);
QPointer對QMetaObject的相關操作做了簡單的封裝,這裡的基本思想是
在QPointer構造的時候呼叫QMetaObject::addGuard(&o)
,把T的指標加入QMetaObject內的一個雜湊表中,
在QPointer析構的時候呼叫QMetaObject::removeGuard(&o)
,把T的指標從雜湊表中刪除。
看程式碼:
QPointer<Test> t = new Test(); // Test類必須繼承QObject
delete t; //物件被delete之後,t會自動置NULL,而不會成為懸掛(dangling)的野指標
if(t.isNull())
qDebug()<<"NULL";
執行後會輸出NULL
。
實際中,QPointer用於解決這樣的問題:在其他地方都用到了某個指標,在這個指標的物件被delete
後,將指標置為空,那麼其他地方的指標會變為野指標。也就是在C++中有這樣的程式碼:
Test* p1 = new Test();
Test* p2 = p1;
delete p1;
p1 = NULL;
if(t2)
qDebug()<<"t2不是NULL";
else
qDebug()<<"t2成為NULL";
執行結果是t2不是NULL
有了QPointer,可以這樣解決這個問題:
Test* t1 = new Test();
QPointer<Test> t2 = t1;
delete t1;
t1 = NULL;
if(t2)
qDebug()<<"t2不是NULL";
else
qDebug()<<"t2成為NULL";
執行結果是t2成為NULL
,t2不再是野指標了。當然這裡的t1最好也用QPointer,不過重點是t2。
QScopedPointer
QScopedPointer類儲存了一個指標,指向在堆上分配的物件,在物件銷燬時delete這個指標。從scope這個詞就可以知道物件指標在出了作用域後就會被delete掉,不必手動delete。這個智慧指標只能在本作用域裡使用,不希望被轉讓。因為它的拷貝構造和賦值操作都是私有的,與QObject及其派生類風格相同。
Test* p = new Test();
QScopedPointer<Test> p2(p);
p2.data()->foo();
p2.take()->foo();
if(p2 == NULL)
qDebug()<<"p2 is null";
else
qDebug()<<"p2 is not null";
執行結果是:p2==NULL
T *QScopedPointer::data() const
返回指向物件的常量指標,QScopedPointer仍擁有物件所有權。
T *QScopedPointer::take()
也是返回物件指標,但QScopedPointer不再擁有物件所有權,而是轉移到呼叫這個函式的caller,同時QScopePointer物件指標置為NULL。注意:如果沒有使用take
,p2會成為野指標。
void QScopedPointer::reset(T *other = Q_NULLPTR)
:delete目前指向的物件,呼叫其解構函式,將指標指向另一個物件other,所有權轉移到other。
以上程式碼僅僅是用於處理new的情況,不能用於malloc和new []陣列。
經常用於這樣的程式碼風格:
class MyPrivateClass; // forward declare MyPrivateClass
class MyClass
{
private:
QScopedPointer<MyPrivateClass> privatePtr; // QScopedPointer to forward declared class
public:
MyClass(); // OK
inline ~MyClass() {} // VIOLATION - Destructor must not be inline
private:
Q_DISABLE_COPY(MyClass) // OK - copy constructor and assignment operators
// are now disabled, so the compiler won't implicitely generate them.
};
在Qt原始碼中,經常用於D指標,例如在qpainter.h
中,有程式碼: QScopedPointer<QPainterPrivate> d_ptr
,以後研究D指標時再深入探討。
QSharedDataPointer
更像普通的指標,也是用於堆上分配的物件,但它是一個計數型只能指標,可以自由拷貝和賦值,可以共享,當此物件被一個QSharedPointer指標指向時,計數加1,少一個QSharedPointer指標指向時,計數減1,一直到計數為0時,物件才會銷燬。
同QScopePointer類似,QSharedPointer也會在離開作用域後刪除指標。
QSharedPointer和QWeakPointer都是執行緒安全的,可以原子地操作指標,不同執行緒可以獲取這兩種指標指向的物件而不必加鎖。
void QSharedPointer::clear()
:清除所指向的物件,如果是最後一個指向,那麼指標會被delete。
data
函式,isNull
函式同QScopePointer功能一樣。
Test* p = new Test();
QSharedPointer<Test> p1(p);
QSharedPointer<Test> p2(p1);
QSharedPointer<Test> p3(p1);
p1.clear();
p2.clear();
p3.clear();
qDebug()<<"3333333";
if(p1.isNull())
qDebug()<<"p1 is null";
else
qDebug()<<"p1 is not null";
return a.exec();
在Qt main函式中測試會遇到事件迴圈的問題,就是執行GUI程式的return a.exec()
實際進入事件迴圈,沒有離開作用域,這種情況下想銷燬物件就需要所有指標都clear。上面的程式碼中,指標計數為3,所以必須三個指標都執行clear
,然後才會銷燬物件,即呼叫解構函式,然後三個指標都成為NULL。有一個指標沒有clear
就不會銷燬物件。
假如不進事件迴圈,而是return 0
,那麼就是按作用域機制,不需要所有指標都clear
也會銷燬物件。