閱讀356 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[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<QWidget *>(obj);


信號和槽機製是Qt的核心內容,而信號和槽機製必須依賴於元對象係統,所以它是Qt中很關鍵的內容。關於元對象係統的知識,可以在Qt中查看The Meta-Object System關鍵字。



三、對象樹與擁有權


Qt中使用對象樹(object tree)來組織和管理所有的QObject類及其子類的對象。當創建一個QObject時,如果使用了其他的對象作為其父對象(parent),那麼這個QObject就會被添加到父對象的children()列表中,這樣當父對象被銷毀時,這個QObject也會被銷毀。實踐表明,這個機製非常適合於管理GUI對象。例如,一個QShortcut(鍵盤快捷鍵)對象是相應窗口的一個子對象,所以當用戶關閉了這個窗口時,這個快捷鍵也可以被銷毀。
       QWidget作為能夠在屏幕上顯示的所有部件的基類,擴展了對象間的父子關係。一個子對象一般也就是一個子部件,因為它們要顯示在父部件的區域之中。例如,當關閉一個消息對話框(message box)後要銷毀它時,消息對話框中的按鈕和標簽也會被銷毀,這也正是我們所希望的,因為按鈕和標簽是消息對話框的子部件。當然,我們也可以自己來銷毀一個子對象。關於這一部分的內容,大家可以在幫助索引中查看Object Trees &Ownership關鍵字。
       在前麵的Qt編程中我們應該看到過很多使用new來創建一個部件,但是卻沒有使用delete來進行釋放的問題。這裏再來研究一下這個問題。


新建Qt Gui應用,項目名稱為“myOwnership”,基類選擇QWidget,然後類名保持“Widget”不變。完成後向項目中添加新文件,模板選擇C++ Class,類名為“MyButton”,基類為“QPushButton”,類型信息選擇“繼承自QWidget”。添加完文件後在mybutton.h文件中添加析構函數的聲明:


~MyButton();


然後到mybutton.cpp文件中添加頭文件#include <QDebug>並定義析構函數:


MyButton::~MyButton()
{
   qDebug() << "delete button";
}


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


#include "mybutton.h"
#include<QDebug>


在構造函數中添加代碼:


MyButton *button = new MyButton(this);    // 創建按鈕部件,指定widget為父部件
button->setText(tr("button"));


更改析構函數:


Widget::~Widget()
{
    delete ui;    qDebug() << "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 <QHBoxLayout>,然後在構造函數中繼續添加代碼:


MyButton *button2 = new MyButton;
MyButton *button3 = new MyButton;
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(button2);
layout->addWidget(button3);
setLayout(layout);      // 在該窗口中使用布局管理器


       這裏創建了兩個MyButton和一個水平布局管理器,但是並沒有指定它們的父部件,現在各個部件的擁有權(ownership)不是很清楚。但是當使用布局管理器來管理這兩個按鈕,並且在窗口中使用這個布局管理器後,這兩個按鈕和水平布局管理器都將重定義父部件而成為窗口Widget的子部件。可以使用children()函數來獲取一個部件的所有子部件的列表,例如在構造函數中再添加如下代碼:


qDebug() << children();    // 輸出所有子部件的列表


       這時大家可以運行一下程序,查看應用程序輸出欄中的信息,然後根據自己的想法更改一下程序,來進一步體會Qt中對象樹的概念。




結語



Qt中的對象樹很好地解決了父子部件的關係,對於Gui編程是十分方便的,在創建部件時我們隻需要關注它的父部件,這樣就不用再考慮其銷毀問題了。下一節,我們將講解Qt中的信號和槽的內容。



涉及到的源碼:  myOwnership.rar (2.02 KB, 下載次數: 3) 


最後更新:2017-04-03 14:54:15

  上一篇:go PHP PDO(mysql) 封裝類
  下一篇:go [Qt教程] 第34篇 網絡(四)FTP(二)