Qt对象树和所有权
什么是对象树
详见官方文档Object Trees & Ownership
。
对象树是Qt基于元对象系统(meta-object system)机制(需要通过moc编译)提供的一种C++拓展特性,在一定程度上解决了内存问题。
对应QObject
对象而言,其构造函数中可以指定自身的父对象指针,这样创建的这个QObject
对象会自动添加到其父对象的children()
列表中,而多个对象间的父子关系是树状的,因此被称为对象树。
Qt能够保证,任何在对象树中的QObject
对象被析构时,如果这个对象有parent,则自动将其从parent的children()
列表中删除;如果有child,则自动析构每一个child。
没有对象树机制会导致的内存释放问题
假设没有对象树机制,考虑如下代码:
#include "mainwindow.h"
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
//此处假设没有对象树机制,传入参数仅代表该对象
//需要显示在w之上
QPushButton *btn = new QPushButton(&w);
btn->move(200, 200);
//不能在此处析构btn对象,否则按钮无法在w上显示
//delete btn;
w.show();
//不能在此处析构btn对象,否则按钮刚在w上显示就被析构了
//delete btn;
return a.exec();
//不能在此处析构btn对象,main函数已经返回,不会执行到此处
//delete btn;
}
可以看到,对应new出来的btn
,我们无法找到合适的位置将其delete,若没有对象树机制,则该代码是内存不安全的。
在栈上创建QObject对象的注意事项,及Qt更推荐在堆上创建QObject对象
在栈上创建QObject对象,并指定父对象时,需要注意对象的析构顺序,考虑如下代码:
#include "mainwindow.h"
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton btn; //btn先被构造
MainWindow w; //w后被构造
btn.setParent(&w); //将btn的父对象设置为w
w.show();
return a.exec();
} //w先出作用域被析构,
//因为对象树的存在,w将销毁子对象btn。
//btn后出作用域被析构,
//btn被析构两次,程序崩溃。
可以看到,在Qt的对象树下对象在栈上的构造顺序是有严格要求的,且其安全性将由程序员控制。
而创建在堆上的对象则没有这种烦恼,因此在Qt中推荐在堆上构建QObject
对象,在指定父对象后,就不需要手动释放了。例如:
#include "mainwindow.h"
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton *btn =
new QPushButton(); //btn先被构造
MainWindow w; //w后被构造
btn->setParent(&w); //将btn的父对象设置为w
w.show();
return a.exec();
} //w先出作用域被析构,
//因为对象树的存在,w将销毁子对象btn。
//btn是堆上分配的,因此不会自动释放。
//btn仅被析构一次,程序是安全的。
由对象树管理内存需要满足的条件
- 对象必须是QObject类的子类(间接子类也可以)
- 对象必须指定其父对象,即把该对象挂到父对象的对象树上
- 方法1:在构造函数时指定父对象
- 方法2:通过成员函数setParent()设置父对象
查看对象树机制的相关信息
考虑如下代码:
#include "mainwindow.h"
#include <QApplication>
#include <QPushButton>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
qDebug() << "-------w.children-------";
qDebug() << "w.children().size() = " << w.children().size();
for (const auto i : w.children()) {
qDebug() << i;
}
qDebug() << "-------w.children-------";
qDebug() << Qt::endl;
QPushButton *btn =
new QPushButton(); //创建btn对象
qDebug() << "-------btn->parent-------";
qDebug() << btn->parent();
qDebug() << "-------btn->parent-------";
qDebug() << Qt::endl;
btn->setParent(&w); //将w设置为btn的父对象
qDebug() << "-------btn->parent-------";
qDebug() << btn->parent();
qDebug() << "-------btn->parent-------";
qDebug() << Qt::endl;
qDebug() << "-------w.children-------";
qDebug() << "w.children().size() = " << w.children().size();
for (const auto i : w.children()) {
qDebug() << i;
}
qDebug() << "-------w.children-------";
qDebug() << Qt::endl;
w.show();
return a.exec();
}
对应输出:
-------w.children-------
w.children().size() = 4
QMainWindowLayout(0x1775d46a5e0, name = "_layout")
QWidget(0x1775d466a80, name = "centralwidget")
QMenuBar(0x1775d4663f0, name = "menubar")
QStatusBar(0x1775d470f90, name = "statusbar")
-------w.children-------
-------btn->parent-------
QObject(0x0)
-------btn->parent-------
-------btn->parent-------
MainWindow(0x5934fff508, name = "MainWindow")
-------btn->parent-------
-------w.children-------
w.children().size() = 5
QMainWindowLayout(0x1775d46a5e0, name = "_layout")
QWidget(0x1775d466a80, name = "centralwidget")
QMenuBar(0x1775d4663f0, name = "menubar")
QStatusBar(0x1775d470f90, name = "statusbar")
QPushButton(0x1775d481320)
-------w.children-------
可以看到,在指定父对象后,w的children列表中多出了一项。