QT5 學習之路14 物件模型

2021-07-05 13:50:24 字數 2862 閱讀 2908

標準 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++ 增加了一些特性:

訊號槽機制,用於解決物件之間的通訊,這個我們已經了解過了,可以認為是 qt 最明顯的特性之一;

可查詢,並且可設計的物件屬性;

強大的事件機制以及事件過濾器;

基於上下文的字串翻譯機制(國際化),也就是 tr() 函式,我們簡單地介紹過;

複雜的定時器實現,用於在事件驅動的 gui 中嵌入能夠精確控制的任務整合;

層次化的可查詢的物件樹,提供一種自然的方式管理物件關係。

智慧型指標(qpointer),在物件析構之後自動設為 0,防止野指標;

能夠跨越庫邊界的動態轉換機制。

通過繼承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(快捷鍵)物件作為其子物件。當我們刪除按鈕的時候,這個快捷鍵理應被刪除。這是合理的。

qwidget是能夠在螢幕上顯示的一切元件的父類。qwidget繼承自qobject,因此也繼承了這種物件樹關係。乙個孩子自動地成為父元件的乙個子元件。因此,它會顯示在父元件的座標系統中,被父元件的邊界剪裁。例如,當使用者關閉乙個對話方塊的時候,應用程式將其刪除,那麼,我們希望屬於這個對話方塊的按鈕、圖示等應該一起被刪除。事實就是如此,因為這些都是對話方塊的子元件。

當然,我們也可以自己刪除子物件,它們會自動從其父物件列表中刪除。比如,當我們刪除了乙個工具欄時,其所在的主視窗會自動將該工具欄從其子物件列表中刪除,並且自動調整螢幕顯示。

我們可以使用qobject::dumpobjecttree()和qobject::dumpobjectinfo()這兩個函式進行這方面的除錯。

qt 引入物件樹的概念,在一定程度上解決了記憶體問題。

當乙個qobject物件在堆上建立的時候,qt 會同時為其建立乙個物件樹。不過,物件樹中物件的順序是沒有定義的。這意味著,銷毀這些物件的順序也是未定義的。qt 保證的是,任何物件樹中的 qobject物件 delete 的時候,如果這個物件有 parent,則自動將其從 parent 的children()列表中刪除;如果有孩子,則自動 delete 每乙個孩子。qt 保證沒有qobject會被 delete 兩次,這是由析構順序決定的。

如果qobject在棧上建立,qt 保持同樣的行為。正常情況下,這也不會發生什麼問題。來看下下面的**片段:

作為父元件的 window 和作為子元件的 quit 都是qobject的子類(事實上,它們都是qwidget的子類,而qwidget是qobject的子類)。這段**是正確的,quit 的析構函式不會被呼叫兩次,因為標準 c++ (iso/iec 14882:2003)要求,區域性物件的析構順序應該按照其建立順序的相反過程。因此,這段**在超出作用域時,會先呼叫 quit 的析構函式,將其從父物件 window 的子物件列表中刪除,然後才會再呼叫 window 的析構函式。

但是,如果我們使用下面的**

情況又有所不同,析構順序就有了問題。我們看到,在上面的**中,作為父物件的 window 會首先被析構,因為它是最後乙個建立的物件。在析構過程中,它會呼叫子物件列表中每乙個物件的析構函式,也就是說, quit 此時就被析構了。然後,**繼續執行,在 window 析構之後,quit 也會被析構,因為 quit 也是乙個區域性變數,在超出作用域的時候當然也需要析構。但是,這時候已經是第二次呼叫 quit 的析構函式了,c++ 不允許呼叫兩次析構函式,因此,程式崩潰了。

由此我們看到,qt 的物件樹機制雖然幫助我們在一定程度上解決了記憶體問題,但是也引入了一些值得注意的事情。這些細節在今後的開發過程中很可能時不時跳出來煩擾一下,所以,我們最好從開始就養成良好習慣,在 qt 中,盡量在構造的時候就指定 parent 物件,並且大膽在堆上建立

Qt 學習之路 2(10) 物件模型

標準 c 物件模型在執行時效率方面卓有成效,但是在某些特定問題域下的靜態特性就顯得捉襟見肘。gui 介面需要同時具有執行時的效率以及更高階別的靈活性。為了解決這一問題,qt 擴充套件 了標準 c 所謂 擴充套件 實際是在使用標準 c 編譯器編譯 qt 源程式之前,qt 先使用乙個叫做 moc met...

QT5學習筆記(3 2) QT物件模型

在qt中建立物件的時候會提供乙個parent物件指標,下面來解釋這個parent到底是幹什麼的。qobject是以物件樹的形式組織起來的。當你建立乙個qobject物件時,會看到qobject的建構函式接收乙個qobject指標作為引數,這個引數就是 parent,也就是父物件指標。這相當於,在建立...

QT5 學習之路22 事件過濾器

有時候,物件需要檢視 甚至要攔截傳送到另外物件的事件。例如,對話方塊可能想要攔截按鍵事件,不讓別的元件接收到 或者要修改回車鍵的預設處理。通過前面的章節,我們已經知道,qt 建立了qevent事件物件之後,會呼叫qobject的event 函式處理事件的分發。顯然,我們可以在event 函式中實現攔...