QT对象管理系统

前言

我们在写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的源码,了解到控件自动管理的两种途径:

  1. 构造函数中传入parent
  2. 将UI元素放到布局中,然后将布局设置到QWidget中
    如果没有采用上述途径之一,那很有可能会造成内存泄漏。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值