1. 程式人生 > >Qt記憶體管理(三) Qt的智慧指標

Qt記憶體管理(三) Qt的智慧指標

智慧指標則可以在退出作用域時(不管是正常流程離開或是因異常離開)總呼叫delete來析構在堆上動態分配的物件。Qt常用的智慧指標有QPointer,QScopedPointer,QSharedPointer。

關於這幾個智慧指標,網上的部落格基本不是翻譯Qt文件,就是翻譯老外的部落格,比較失望。

QPointer

QPointer屬於Qt物件模型的特性,本質是一個模板類,它為QObje提供了guarded pointer。當其指向的物件(必須是QObject及其派生類)被銷燬時,它會被自動置NULL,原理是其物件析構時會執行QObject的解構函式,進而執行QObjectPrivate::clearGuards(this);

,這也是基於其指向物件都繼承自QObject的原因。

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

,也就是說t2成為了野指標。

有了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也會銷燬物件。