標準 C++ 對象模型在運行時效率方面卓有成效,可是在某些特定問題域下的靜態特性就顯得捉襟見肘。GUI 界面須要同時具備運行時的效率以及更高級別的靈活性。爲了解決這一問題,Qt 「擴展」了標準 C++。所謂「擴展」,實際是在使用標準 C++ 編譯器編譯 Qt 源程序以前,Qt 先使用一個叫作 moc(Meta Object Compiler,元對象編譯器)的工具,先對 Qt 源代碼進行一次預處理(注意,這個預處理與標準 C++ 的預處理有所不一樣。Qt 的 moc 預處理髮生在標準 C++ 預處理器工做以前,而且 Qt 的 moc 預處理不是遞歸的。),生成標準 C++ 源代碼,而後再使用標準 C++ 編譯器進行編譯。若是你曾經爲信號函數這樣的語法感到奇怪(如今咱們已經編譯過一些 Qt 程序,你應當注意到了,信號函數是不須要編寫實現代碼的,那怎麼能夠經過標準 C++ 的編譯呢?),這其實就是 moc 進行了處理以後的效果。編程
Qt 使用 moc,爲標準 C++ 增長了一些特性:函數
經過繼承QObject
類,咱們能夠很方便地得到這些特性。固然,這些特性都是由 moc 幫助咱們實現的。moc 其實實現的是一個叫作元對象系統(meta-object system)的機制。正如上面所說,這是一個標準 C++ 的擴展,使得標準 C++ 更適合於進行 GUI 編程。雖然利用模板能夠達到相似的效果,可是 Qt 沒有選擇使用模板。按照 Qt 官方的說法,模板雖然是內置語言特性,可是其語法實在是複雜,而且因爲 GUI 是動態的,利用靜態的模板機制有時候很難處理。而本身使用 moc 生成代碼更爲靈活,雖然效率有些下降(一個信號槽的調用大約至關於四個模板函數調用),不過在現代計算機上,這點性能損耗實在是能夠忽略。工具
在本節中,咱們將主要介紹 Qt 的對象樹。還記得咱們前面在MainWindow
的例子中看到了 parent 指針嗎?如今咱們就來解釋這個 parent 究竟是幹什麼的。性能
QObject
是以對象樹的形式組織起來的。當你建立一個QObject
對象時,會看到QObject
的構造函數接收一個QObject
指針做爲參數,這個參數就是 parent,也就是父對象指針。這至關於,在建立QObject
對象時,能夠提供一個其父對象,咱們建立的這個QObject
對象會自動添加到其父對象的children()
列表。當父對象析構的時候,這個列表中的全部對象也會被析構。(注意,這裏的父對象並非繼承意義上的父類!)這種機制在 GUI 程序設計中至關有用。例如,一個按鈕有一個QShortcut
(快捷鍵)對象做爲其子對象。當咱們刪除按鈕的時候,這個快捷鍵理應被刪除。這是合理的。ui
QWidget
是可以在屏幕上顯示的一切組件的父類。QWidget
繼承自QObject
,所以也繼承了這種對象樹關係。一個孩子自動地成爲父組件的一個子組件。所以,它會顯示在父組件的座標系統中,被父組件的邊界剪裁。例如,當用戶關閉一個對話框的時候,應用程序將其刪除,那麼,咱們但願屬於這個對話框的按鈕、圖標等應該一塊兒被刪除。事實就是如此,由於這些都是對話框的子組件。翻譯
固然,咱們也能夠本身刪除子對象,它們會自動從其父對象列表中刪除。好比,當咱們刪除了一個工具欄時,其所在的主窗口會自動將該工具欄從其子對象列表中刪除,而且自動調整屏幕顯示。設計
咱們可使用QObject::dumpObjectTree()
和QObject::dumpObjectInfo()
這兩個函數進行這方面的調試。指針
Qt 引入對象樹的概念,在必定程度上解決了內存問題。調試
當一個QObject
對象在堆上建立的時候,Qt 會同時爲其建立一個對象樹。不過,對象樹中對象的順序是沒有定義的。這意味着,銷燬這些對象的順序也是未定義的。Qt 保證的是,任何對象樹中的 QObject
對象 delete 的時候,若是這個對象有 parent,則自動將其從 parent 的children()
列表中刪除;若是有孩子,則自動 delete 每個孩子。Qt 保證沒有QObject
會被 delete 兩次,這是由析構順序決定的。code
若是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 對象,而且大膽在堆上建立。