Qt 对象树与继承关系
QObject对象树
QObject将自己组织在对象树中。当您创建一个以另一个对象作为父对象的QObject时,它将被添加到父对象的children()列表中。因为 QObject 类就有一个私有变量 QList<QObject *>,专门存储这个类的子对象们。
typedef QList<QObject*> QObjectList;
比如创建一个 QObject 并指定父对象时,就会把自己加入到父对象的子对象 children() 列表中,也就是 QList<QObject *> 变量中。并在父对象被删除时被删除。事实证明,这种方法非常适合GUI对象的需求。例如,QLabel(标签)是相关窗口的子级,因此,当用户关闭该窗口时,快捷方式也会被删除。
QQuickItem是Qt Quick模块的基本可视元素,它继承自QObject,但是其可视父对象的概念不同于QObject父对象的概念。项的视觉父项不一定与它的对象父项相同。有关更多详细信息,请参见Qt Quick中的Concepts-Visual Parent。
//由于跟派生的主窗口类名相同,所以使用Ui命名空间区分开来。
//ui文件有uic.exe自动生成ui_mainwindow.h文件
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
//moc元对象宏,使得对象继承信号槽等机制。
//moc.exe会把该头文件自动转换为标准的c++
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
//此处的ui指针不指向任何父对象
Ui::MainWindow *ui;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
//此处new 了一个Ui::MainWindow对象。
, ui(new Ui::MainWindow)
{
//组装界面,this是该窗口指针
//将ui内定义的所有控件指针指向父窗口,而不是UI::MainWinow *ui;
//使得父窗口析构之前,先释放子对象(控件)
//setupUi(this)是由.ui文件生成的类的构造函数,
//这个函数的作用是对界面进行初始化,它按照我们在Qt设计器里设计的样子把窗体画出来,
//把我们在Qt设计器里面定义的信号和槽建立起来。
//也可以说,setupUi是我们画界面和写程序之间的桥梁。
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
//ui指针未指向任何父类,所以需要单独释放回收。
//控件并非跟随ui释放
delete ui;
}
QWidget是Qt Widgets模块的基本类,它扩展了父子关系。通常,子级也将成为子级小部件,即,它在其父级的坐标系中显示,并通过其父级的边界进行图形裁剪。例如,当应用程序在关闭后删除消息框时,消息框的按钮和标签也会被删除,就像我们想要的那样,因为按钮和标签是消息框的子级。Qt 运用对象树模式,当父对象被析构时,子对象自动就 delete 掉了,不用再写一大堆的代码了。
您也可以自己删除子对象,它们将从自己的父对象中删除。例如,当用户delete删除工具栏时,它可能导致应用程序删除其QToolBar对象之一,在这种情况下,工具栏的QMainWindow父级将检测到更改并相应地重新配置其屏幕空间。
当应用程序外观或行为异常时,调试功能QObject :: dumpObjectTree()和QObject :: dumpObjectInfo()通常很有用。
QObject的构建/销毁顺序
当在堆上创建QObject时(即,用new创建),可以以任何顺序从它们构造树,然后,可以以任何顺序销毁树中的对象。删除树中的任何QObject时,如果该对象具有父对象,则析构函数会自动从其父对象中删除该对象。如果对象有子对象,则析构函数会自动删除每个子对象。没有QObject中被删除两次,不管破坏的秩序。
正常情况下,最后被创建出来的会先被析构掉,用代码来演示一下:
int main()
{
QWidget window;
QPushButton quit("Quit", &window);
...
}
父窗口和子窗口quit都是QObject,因为QPushButton继承QWidget, QWidget继承QObject。这段代码是正确的:quit的析构函数不会被调用两次,因为c++语言标准(ISO/IEC 14882:2003)规定本地对象的析构函数被调用的顺序与其构造函数相反。因此,子程序quit的析构函数首先被调用,它在调用window的析构函数之前从父程序window中删除自己。
但是现在考虑一下,如果我们交换构造的顺序会发生什么,如下面的第二个片段所示:
int main()()
{{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
...
}
由于 window 的创建在quit对象之后,程序退出时先调用 window 的析构函数,此时由于 quit 是window的子对象,所以第一次被析构。接着程序再一次调用 quit 的析构函数,这是quit 的第二次被析构,这时由于被两次析构,已经找不到指针,程序崩溃。
这种特殊情况在编程中很隐蔽,不容易发现。因为编译的时候不会报错,只有运行时才会产生问题。所以我们要保持良好的编程习惯以及对事物产生顺序有科学的认知。