一、對象樹的概念
Qt中使用對象樹(object tree)來組織和管理所有的QObject類及其子類的對象。當創建一個QObject時,如果使用了其他的對象作為其父對象(parent),那么這個 QObject就會被添加到父對象的children()列表中,這樣當父對象被銷毀時,這個QObject也會被銷毀。實踐表明,這個機制非常適合於管理GUI對象。例如,一個 QShortcut(鍵盤快捷鍵)對象是相應窗口的一個子對象,所以當用戶關閉了這個窗口 時,這個快捷鍵也可以被銷毀。
QWidget作為能夠在屏幕上顯示的所有部件的基類,擴展了對象間的父子關系。 一個子對象一般也就是一個子部件,因為它們要顯示在父部件的區域之中。例如,當關閉一個消息對話框(message box)后要銷毀它時,消息對話框中的按鈕和標簽也會被銷毀,這也正是我們所希望的,因為按鈕和標簽是消息對話框的子部件。當然,也可以自己來銷毀一個子對象。關於這一部分內容,可以在幫助索引中査看Object Trees & Ownership關鍵字。
二、對象樹的示例程序
新建Qt Gui應用,項目名稱為myOwnership,基類選擇QWidget,然后類名保持Widget不變。完成后向項目中添加新文件,模板選擇C+ +類,類名為MyButton,基類為QPushButton,類型信息選擇“繼 承自QWidget”。添加完文件后將mybuuon. h文件修改如下:
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QPushButton>
#include <QDebug>
class MyButton : public QPushButton
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
~MyButton();
};
#endif // MYBUTTON_H
這里主要是添加了析構函數的聲明。然后到mybutton. cpp文件中,修改如下:
#include "mybutton.h"
MyButton::MyButton(QWidget *parent) :
QPushButton(parent)
{
}
MyButton::~MyButton()
{
qDebug() << "delete button";
}
這里添加了析構函數的定義,這樣當 MyButton 的對象被銷毀時,就會輸出相應的信息。
下面到widget.cpp文件中,修改如下:
#include "widget.h"
#include "ui_widget.h"
#include "mybutton.h"
#include <QDebug>
#include <QHBoxLayout>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//創建按鈕部件,指定widget為父部件
MyButton *button = new MyButton(this);
button->setText(tr("button"));
}
Widget::~Widget()
{
delete ui;
qDebug() << "delete widget";
}
當Widget窗口被銷毀時,將輸出信息。下面運行程序,然后關閉窗口,在Qt Creator的應用程序輸出欄中的輸出信息為:
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操作符來銷毀該對象了。
三、元對象系統的概念
Qt中的元對象系統(Meta-Object System)提供了對象間通信的信號和槽機制、運行時類型信息和動態屬性系統。元對象系統是基於以下3個條件的:
- 該類必須繼承自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: :newlnstance()構造該類的一個新實例。
除了這些特性,還可以使用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關鍵字。