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列表中多出了一项。