不知道刚接触Qt的小伙伴有没有这样的疑问,在Qt中new出来的对象没有手动delete来释放,难道Qt中不需要释放?Qt还是基于C++的框架,肯定还是需要delete的,答案就是Qt的对象树模型。通过对象树可以在父对象被析构的时候,自动地析构子对象。当然要支持对象树,需要继承QObject或QObject的子类。我们现在就来看看是怎么实现对象树的。
QObject有一个成员d_ptr,它的定义如图1所示(参看qobject.h):

QObjectData有一个成员children来保存子对象,它的定义如图2所示(参看qobject.h):

QObjectList其实就是QList<QObject*>,在qobject.h中可以看到typedef QList<QObject*> QObjectList。当QObject被释放的时候就会把children中的子对象一一的delete掉。在QObject的析构函数中会有这么一段代码,如图3所示:

d是一个指针(关于Qt中D指针和Q指针,以后再讲),他的类型是QObjectPrivate。而QObjectPrivate是继承QObjectData,参看qobject_p.h。deleteChildrend的实现如图4所示(参看qobject.cpp):

现在我清楚了它的释放流程,现在剩下的就是Qt怎么把子对象放到children列表中,这就要从QObject的构造函数和成员函数setParent函数说起了。QObject的构造函数如图5所示(参看qobject.h):

可以看到在构造函数参数中有一个parent,如果不为空的时候,就是把该对象保存到parent的children列表中,如图6所示(参看qobject.cpp):

在setParent中会调用QObjectPrivate::setParent_helper,它的实现如图7所示(参看qobject.cpp):

首先需要在o的原来的父对象的children列表中把o删除,如果不删除的话,这样就有2个父对象都保存了o的指针,这样在析构的时候就会出现o被delete 2次的情况。然后再把o加到新的父对象的children列表中。通过上面的方式就把子对象的指针保存到了父对象的children中了。值得注意的是,如果在构造时设置父对象为 nullptr,那么当前对象不会有父对象存在,Qt 也不会自动释放该对象,除非超出作用域导致析构函数被调用,或者用户在恰当时机使用 delete 操作符或者使用 QObject::deleteLater 方法。
那如果我们先把子对象释放了会发生什么,会不会出现父对象再析构的时候再一次的释放该对象?答案是不会的,原因就在QObject对象在析构的时候会把自己从父对象的children列表中自动的删除掉,如图8所示:

setParent_helper是实现参看图7,他首先就是从父对象的children列表中删除。
如果要获取子对象,可以通过如图9所示的函数获取

其中children获取所有的子对象,findChild和findChildren是模板函数,可以获取特定类型的子对象。如果获取QPushButton类型的子对象
QList<QPushButton *> allPButtons = parentWidget.findChildren<QPushButton *>();
使用对象树存在的一个问题,我们都知道C++中规定了析构顺序应该按照其创建顺序的相反过程,也就是说先创建的对象会后析构。我们看一段代码,如图10所示:

如果关闭窗口会导致程序异常结束,为什么会出现这样的情况?各位聪明小伙伴根据前面的知识应该能想出来,如果大家有什么不明白的,可以在评论区告诉我。为了避免这个问题我们可以这么做:
- 先创建父对象再创建子类对象,并且在创建子对象时就指定父对象;
- 尽量在堆上创建子对象;
如果类型不是QObject或QObject继承类型怎么管理,这个就可以使用智能指针了,关于智能指针(QScopedPointer和QSharedPointer)的使用会在后面的文章讲解。