前言
我们在写QT界面代码时,经常会像下面这样只有new,没有delete
Widget::Widget(QWidget* parent)
: QWidget(parent)
{
auto label = new QLabel("test", this);
auto button = new QPushButton("click");
auto layout = new QHBoxLayout;
layout->addWidget(label);
layout->addWidget(button);
setLayout(layout);
}
上面new了三个元素,但一个delete都没有,而且label、button、layout 还都是局部变量 。
对于QT老司机来说,这很简单;但对于新手来说,就有点不太明白,下面我们就从源码的角度来看QT是如何管理对象的。
QObject对象管理
在UI开发中,会涉及大量的UI元素创建,如果全都是手动delete,那在析构函数中会有大量的delete,而且很可能有遗漏。
实际情况中,每一个UI元素大都不是独立存在的,都是有一个父子关系。那就可以借鉴RAII的思路,由父元素来管理子元素的生命周期:将每一个创建出来的子元素指针交由父元素管理,父元素析构时自动析构所有子元素。
QT也确实是这样做的,我们来看QObject源码:
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
{
...
setParent(parent);
...
}
void QObject::setParent(QObject *parent)
{
...
d->setParent_helper(parent);
}
将构造函数的父元素指针parent传给了d->setParent_helper
:
void QObjectPrivate::setParent_helper(QObject *o)
{
...
parent->d_func()->children.append(q);
...
}
到这里,大家应该看明白了,上面这一行代码可以简单理解为parent->children.appent(this)
,就是将自己放到父元素的children列表中。
其中children是QObjectData的一个数据成员,QObjectData又是QObject的数据成员,如下:
class Q_CORE_EXPORT QObjectData {
...
QObjectList children;
...
}
class Q_CORE_EXPORT QObject
{
...
QScopedPointer<QObjectData> d_ptr;
...
}
我们再看QObject析构:
QObject::~QObject()
{
...
if (!d->children.isEmpty())
d->deleteChildren();
...
if (d->parent) // remove it from parent object
d->setParent_helper(0);
...
}
void QObjectPrivate::deleteChildren()
{
...
for (int i = 0; i < children.count(); ++i) {
currentChildBeingDeleted = children.at(i);
children[i] = 0;
delete currentChildBeingDeleted;
}
children.clear();
...
}
可以看到,~QObject()
调用deleteChildren
来删除了全部的子元素。
另外,要注意的一点是d->setParent_helper(0)
,在一个元素析构时要将自己从parent的children中去掉,否则parent的children中放的指针就成了野指针。
到这里,可以解释auto label = new QLabel("test", this);
中label不需要手动删除的问题,原因就是:QLabel构造函数中parent参数是this,就是说将label指针放到了父控件widget的children中去管理。
但是auto button = new QPushButton("click");
中button构造函数并未传this,那是怎么管理的呢?
QWidget对象管理
其实QWidget元素管理还是基于QObject的children来起作用的。
不过首先我们先看QLayout
QLayout的中转作用
创建button虽然没有指定this,但是我们调用了layout->addWidget(button)
把button加到了layout中,我们来看layout是如何进行对象管理的:
void QLayout::addWidget(QWidget *w)
{
addChildWidget(w);
addItem(QLayoutPrivate::createWidgetItem(this, w));
}
这里涉及两个函数
void QLayout::addChildWidget(QWidget *w)
{
QWidget *mw = parentWidget();
QWidget *pw = w->parentWidget();
...
if (!pw && mw)
w->setParent(mw);
...
}
如果layout有父元素但是w没,则将w的父元素设置为layout的,我们示例代码中layout是没有父元素的。我们接着看addItem(QLayoutPrivate::createWidgetItem(this, w));:
由于addItem是纯虚函数,我们看QHBoxLayout的实现:
void QBoxLayout::addItem(QLayoutItem *item)
{
...
QBoxLayoutItem *it = new QBoxLayoutItem(item);
d->list.append(it);
...
}
将包含元素指针的QLayoutItem放到了list中,到这里线索断了~~。
从示例代码看,我们得去看QWidget的setLayout
了。
回归QWidget的怀抱
void QWidget::setLayout(QLayout *l)
{
...
d->layout = l;
if (oldParent != this) {
l->setParent(this);
l->d_func()->reparentChildWidgets(this);
...
}
}
关键代码在l->d_func()->reparentChildWidgets(this);
,又回到了QLayout的reparentChildWidgets
。
void QLayoutPrivate::reparentChildWidgets(QWidget *mw)
{
...
int n = q->count();
...
for (int i = 0; i < n; ++i) {
QLayoutItem *item = q->itemAt(i);
if (QWidget *w = item->widget()) {
QWidget *pw = w->parentWidget();
...
if (pw != mw)
w->setParent(mw);
...
} else if (QLayout *l = item->layout()) {
l->d_func()->reparentChildWidgets(mw);
}
}
}
在QBoxLayout::addItem
函数里,将包含元素指针的QLayoutItem放到了list中,到这里,就是把list中的QLayoutItem 全部遍历一遍通过调用w->setParent(mw);
将每一个QLayoutItem 中的UI元素的父元素设置为layout的父元素,layout中的元素最终还是被传递到widget中了。
看到这里,大家应该明白了示例中button是如何被管理的。
总结
我们通过看QObject、QWidget、QLayout的源码,了解到控件自动管理的两种途径:
- 构造函数中传入parent
- 将UI元素放到布局中,然后将布局设置到QWidget中
如果没有采用上述途径之一,那很有可能会造成内存泄漏。