天天看點

[Qt教程] 第43篇 進階(三)對象樹與擁有權 [Qt教程] 第43篇 進階(三)對象樹與擁有權

<a target="_blank" href="http://bbs.qter.org/forum.php?mod=viewthread&amp;tid=696">樓主</a>

[Qt教程] 第43篇 進階(三)對象樹與擁有權 [Qt教程] 第43篇 進階(三)對象樹與擁有權

 發表于 2013-9-12 16:39:33 | 檢視:

255| 回複: 1

對象樹與擁有權

版權聲明

該文章原創于qter開源社群

導語

學習完前面的内容,大家對應用qt程式設計應該已經有了一個大概的印象。後面的内容我們将介紹qt中的一些核心機制,它們是構成qt的基礎,包括對象模型、信号和槽、對象樹與擁有權等。在前面使用qt程式設計時,大家對一些内容可能存在疑惑,學習完下面的知識,可以幫助大家更好的使用qt進行程式設計。

環境:windows xp + qt 4.8.5+qtcreator2.8.0

目錄

一、對象模型

二、元對象系統

三、對象樹與擁有權

正文

标準c++對象模型可以在運作時非常有效的支援對象範式(object paradigm),但是它的靜态特性在一些問題領域中不夠靈活。圖形使用者界面程式設計不僅需要運作時的高效性,還需要高度的靈活性。為此,qt在标準c++對象模型的基礎上添加了一些特性,形成了自己的對象模型。這些特性有:

一個強大的無縫對象通信機制——信号和槽(signals and slots);

可查詢和可設計的對象屬性系統(object properties);

強大的事件和事件過濾器(events and event filters);

通過上下文進行國際化的字元串翻譯機制(string translation for internationalization);

完善的定時器(timers)驅動,使得可以在一個事件驅動的gui中處理多個任務;

分層結構的、可查詢的對象樹(object trees),它使用一種很自然的方式來組織對象擁有權(object ownership);

守衛指針即qpointer,它在引用對象被銷毀時自動将其設定為0;

動态的對象轉換機制(dynamic cast);

qt的這些特性都是在遵循标準c++規範内實作的,使用這些特性都必須要繼承自qobject類。其中對象通信機制和動态屬性系統,還需要元對象系統(meta-objectsystem)的支援。關于對象模型的介紹,大家可以在幫助中檢視object model關鍵字。

qt中的元對象系統(meta-object system)提供了對象間通信的信号和槽機制、運作時類型資訊和動态屬性系統。元對象系統是基于以下三個條件的:

該類必須繼承自qobject類;

必須在類的私有聲明區聲明q_object宏(在類定義時,如果沒有指定public或者private,則預設為private);

元對象編譯器meta-object compiler(moc),為qobject的子類實作元對象特性提供必要的代碼。

其中moc工具讀取一個c++源檔案,如果它發現一個或者多個類的聲明中包含有q_object宏,便會另外建立一個c++源檔案(就是在項目目錄中的debug目錄下看到的以moc開頭的c++源檔案),其中包含了為每一個類生成的元對象代碼。這些産生的源檔案或者被包含進類的源檔案中,或者和類的實作同時進行編譯和連結。

元對象系統主要是為了實作信号和槽機制才被引入的,不過除了信号和槽機制以外,元對象系統還提供了其他一些特性:

qobject::metaobject()函數可以傳回一個類的元對象,它是qmetaobject類的對象;

qmetaobject::classname()可以在運作時以字元串形式傳回類名,而不需要c++編輯器原生的運作時類型資訊(rtti)的支援;

qobject::inherits()函數傳回一個對象是否是qobject繼承樹上一個類的執行個體的資訊;

qobject::tr()和qobject::trutf8()進行字元串翻譯來實作國際化;

qobject::setproperty()和qobject::property()通過名字來動态設定或者擷取對象屬性;

qmetaobject::newinstance()構造該類的一個新執行個體。

除了這些特性外,還可以使用qobject_cast()函數來對qobject類進行動态類型轉換,這個函數的功能類似于标準c++中的dynamic_cast()函數,但它不再需要rtti的支援。這個函數嘗試将它的參數轉換為尖括号中的類型的指針,如果是正确的類型則傳回一個非零的指針,如果類型不相容則傳回0。例如:

qobject *obj = new mywidget;

qwidget *widget = qobject_cast&lt;qwidget *&gt;(obj);

信号和槽機制是qt的核心内容,而信号和槽機制必須依賴于元對象系統,是以它是qt中很關鍵的内容。關于元對象系統的知識,可以在qt中檢視the

meta-object system關鍵字。

qt中使用對象樹(object tree)來組織和管理所有的qobject類及其子類的對象。當建立一個qobject時,如果使用了其他的對象作為其父對象(parent),那麼這個qobject就會被添加到父對象的children()清單中,這樣當父對象被銷毀時,這個qobject也會被銷毀。實踐表明,這個機制非常适合于管理gui對象。例如,一個qshortcut(鍵盤快捷鍵)對象是相應視窗的一個子對象,是以當使用者關閉了這個視窗時,這個快捷鍵也可以被銷毀。

       qwidget作為能夠在螢幕上顯示的所有部件的基類,擴充了對象間的父子關系。一個子對象一般也就是一個子部件,因為它們要顯示在父部件的區域之中。例如,當關閉一個消息對話框(message box)後要銷毀它時,消息對話框中的按鈕和标簽也會被銷毀,這也正是我們所希望的,因為按鈕和标簽是消息對話框的子部件。當然,我們也可以自己來銷毀一個子對象。關于這一部分的内容,大家可以在幫助索引中檢視object

trees &amp;ownership關鍵字。

       在前面的qt程式設計中我們應該看到過很多使用new來建立一個部件,但是卻沒有使用delete來進行釋放的問題。這裡再來研究一下這個問題。

建立qt gui應用,項目名稱為“myownership”,基類選擇qwidget,然後類名保持“widget”不變。完成後向項目中添加新檔案,模闆選擇c++

class,類名為“mybutton”,基類為“qpushbutton”,類型資訊選擇“繼承自qwidget”。添加完檔案後在mybutton.h檔案中添加析構函數的聲明:

~mybutton();

然後到mybutton.cpp檔案中添加頭檔案#include &lt;qdebug&gt;并定義析構函數:

mybutton::~mybutton()

{

   qdebug() &lt;&lt; "delete button";

}

       這樣當mybutton的對象被銷毀時,就會輸出相應的資訊。這裡定義析構函數,隻是為了更清楚的看到部件的銷毀過程,其實一般在建構新類時不需要實作析構函數。下面在widget.cpp檔案中進行更改,添加頭檔案:

#include "mybutton.h"

#include&lt;qdebug&gt;

在構造函數中添加代碼:

mybutton *button = new mybutton(this); 

  // 建立按鈕部件,指定widget為父部件

button-&gt;settext(tr("button"));

更改析構函數:

widget::~widget()

    delete ui; 

  qdebug() &lt;&lt; "delete widget";}

       widget類的析構函數中預設的已經有了銷毀ui的語句,這裡又添加了輸出語句。當widget視窗被銷毀時,将輸出資訊。下面運作程式,然後關閉視窗,在qtcreator的應用程式輸出欄中的輸出資訊為:

delete widget

delete button

       可以看到,當關閉視窗後,因為該視窗是頂層視窗,是以應用程式要銷毀該視窗部件(如果不是頂層視窗,那麼關閉時隻是隐藏,不會被銷毀),而當視窗部件銷毀時會自動銷毀其子部件。這也就是為什麼在qt中經常隻看到new操作而看不到delete操作的原因。再來看一下main.cpp檔案,其中widget對象是建立在棧上的:

widget w;

w.show();

       這樣對于對象w,在關閉程式時會被自動銷毀。而對于widget中的部件,如果是在堆上建立(使用new操作符),那麼隻要指定widget為其父視窗就可以了,也不需要進行delete操作。整個應用程式關閉時,會去銷毀w對象,而此時又會自動銷毀它的所有子部件,這些都是qt的對象樹所完成的。

       是以,對于規範的qt程式,我們要在main()函數中将主視窗部件建立在棧上,例如“widget

w;”,而不要在堆上進行建立(使用new操作符)。對于其他視窗部件,可以使用new操作符在堆上進行建立,不過一定要指定其父部件,這樣就不需要再使用delete操作符來銷毀該對象了。

       還有一種重定義父部件(reparented)的情況,例如,将一個包含其他部件的布局管理器應用到視窗上,那麼該布局管理器和其中的所有部件都會自動将它們的父部件轉換為該視窗部件。在widget.cpp檔案中添加頭檔案#include &lt;qhboxlayout&gt;,然後在構造函數中繼續添加代碼:

mybutton *button2 = new mybutton;

mybutton *button3 = new mybutton;

qhboxlayout *layout = new qhboxlayout;

layout-&gt;addwidget(button2);

layout-&gt;addwidget(button3);

setlayout(layout);      // 在該視窗中使用布局管理器

       這裡建立了兩個mybutton和一個水準布局管理器,但是并沒有指定它們的父部件,現在各個部件的擁有權(ownership)不是很清楚。但是當使用布局管理器來管理這兩個按鈕,并且在視窗中使用這個布局管理器後,這兩個按鈕和水準布局管理器都将重定義父部件而成為視窗widget的子部件。可以使用children()函數來擷取一個部件的所有子部件的清單,例如在構造函數中再添加如下代碼:

qdebug() &lt;&lt; children();    // 輸出所有子部件的清單

       這時大家可以運作一下程式,檢視應用程式輸出欄中的資訊,然後根據自己的想法更改一下程式,來進一步體會qt中對象樹的概念。

結語

qt中的對象樹很好地解決了父子部件的關系,對于gui程式設計是十分友善的,在建立部件時我們隻需要關注它的父部件,這樣就不用再考慮其銷毀問題了。下一節,我們将講解qt中的信号和槽的内容。

涉及到的源碼: 

[Qt教程] 第43篇 進階(三)對象樹與擁有權 [Qt教程] 第43篇 進階(三)對象樹與擁有權

kb, 下載下傳次數: 3)