Qt 棧中物件樹結構問題
Qt 引入物件樹的概念,在一定程度上解決了記憶體問題。
當一個QObject物件在堆上建立的時候,Qt 會同時為其建立一個物件樹。不過,物件樹中物件的順序是沒有定義的。這意味著,銷燬這些物件的順序也是未定義的。Qt 保證的是,任何物件樹中的 QObject物件 delete 的時候,如果這個物件有 parent,則自動將其從 parent 的children()列表中刪除;如果有孩子,則自動 delete 每一個孩子。Qt 保證沒有QObject會被 delete 兩次,這是由析構順序決定的。
如果QObject在棧上建立,Qt 保持同樣的行為。正常情況下,這也不會發生什麼問題。來看下下面的程式碼片段:
{
QWidget window;
QPushButton quit("Quit", &window);
}
作為父元件的 window 和作為子元件的 quit 都是QObject的子類(事實上,它們都是QWidget的子類,而QWidget是QObject的子類)。這段程式碼是正確的,quit 的解構函式不會被呼叫兩次,因為標準 C++ (ISO/IEC 14882:2003)要求,區域性物件的析構順序應該按照其建立順序的相反過程。因此,這段程式碼在超出作用域時,會先呼叫 quit 的解構函式,將其從父物件 window 的子物件列表中刪除,然後才會再呼叫 window 的解構函式。
但是,如果我們使用下面的程式碼:
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
}
情況又有所不同,析構順序就有了問題。我們看到,在上面的程式碼中,作為父物件的 window 會首先被析構,因為它是最後一個建立的物件。在析構過程中,它會呼叫子物件列表中每一個物件的解構函式,也就是說, quit 此時就被析構了。然後,程式碼繼續執行,在 window 析構之後,quit 也會被析構,因為 quit 也是一個區域性變數,在超出作用域的時候當然也需要析構。但是,這時候已經是第二次呼叫 quit 的析構函數了,C++ 不允許呼叫兩次解構函式,因此,程式崩潰了。
由此我們看到,Qt 的物件樹機制雖然幫助我們在一定程度上解決了記憶體問題,但是也引入了一些值得注意的事情。這些細節在今後的開發過程中很可能時不時跳出來煩擾一下,所以,我們最好從開始就養成良好習慣,在 Qt 中,儘量在構造的時候就指定 parent 物件,並且大膽在堆上建立。