对象树机制
Qt提供了一种机制,在析构父对象时,会先析构它的子对象。完成这种机制需要两个条件:
1、继承自QObject类。
2、指定parent确认父子关系。
布局管理
我们先做一个小实验,我们要把一个QLineEdit显示到Widget上。当然我们可以直接new一个QLineEdit对象,并在它的构造函数传入this指针来实现,还可以使用move函数来移动它的位置。
m_pLineEdit = new QLineEdit(this);
m_pLineEdit->move(100,100);
在这里我们要用水平布局来实现。
不指定parent,也不采用布局是不会在界面上显示QLineEdit的。
m_pLineEdit = new QLineEdit;
不指定parent,采用布局实现
m_pLineEdit = new QLineEdit;
QHBoxLayout *m_pHBoxLayout = new QHBoxLayout;
m_pHBoxLayout->setMargin(10);
m_pHBoxLayout->setSpacing(10);
m_pHBoxLayout->addWidget(m_pLineEdit);
this->setLayout(m_pHBoxLayout);
我们再看一个例子
m_pLineEdit = new QLineEdit;
QHBoxLayout m_pHBoxLayout;
m_pHBoxLayout.setMargin(10);
m_pHBoxLayout.setSpacing(10);
m_pHBoxLayout.addWidget(m_pLineEdit);
this->setLayout(&m_pHBoxLayout);
好,现在我们来看两个问题:
1、第一个例子中的m_pHBoxLayout在堆上创建,我们想要delete掉它时怎么找到这个对象?
2、第二个例子中m_pHBoxLayout在栈上创建,似乎没起到布局作用,但是控件为什么显示到了界面上?
首先,通过以上对比,不难发现,实现我们想要的效果,m_pHBoxLayout的生命周期必须是界面的生命周期,不可以在栈上创建,因为跳出作用域后,它就会被析构掉,所以只能创建在堆上,也难怪各种教程上都是用的指针对象,是由问题本身的特点决定的。
我们明确了m_pHBoxLayout必须得new出来,那么怎么找到它并delete掉呢?聪明得同学想到,把它作为类成员,就可以了。是的,这样可以解决问题。那那么多教程都是以上这么写得,难道他们没有考虑内存泄漏?
这时候我们想到对象树机制,如果是这个机制,那么当界面析构得时候,会把它得子对象m_pHBoxLayout先析构掉,这样就不会造成内存泄漏。
于是,我去查看了setLayout的源码,果然!!
void QWidget::setLayout(QLayout *l)
{
if (!l) {
qWarning("QWidget::setLayout: Cannot set layout to 0");
return;
}
if (layout()) {
if (layout() != l)
qWarning("QWidget::setLayout: Attempting to set QLayout \"%s\" on %s \"%s\", which already has a"
" layout", l->objectName().toLocal8Bit().data(), metaObject()->className(),
objectName().toLocal8Bit().data());
return;
}
QObject *oldParent = l->parent();
if (oldParent && oldParent != this) {
if (oldParent->isWidgetType()) {
// Steal the layout off a widget parent. Takes effect when
// morphing laid-out container widgets in Designer.
QWidget *oldParentWidget = static_cast<QWidget *>(oldParent);
oldParentWidget->takeLayout();
} else {
qWarning("QWidget::setLayout: Attempting to set QLayout \"%s\" on %s \"%s\", when the QLayout already has a parent",
l->objectName().toLocal8Bit().data(), metaObject()->className(),
objectName().toLocal8Bit().data());
return;
}
}
Q_D(QWidget);
l->d_func()->topLevel = true;
d->layout = l;
if (oldParent != this) {
l->setParent(this);
l->d_func()->reparentChildWidgets(this);
l->invalidate();
}
if (isWindow() && d->maybeTopData())
d->topData()->sizeAdjusted = false;
}
题外话:QWidget::setLayout: Attempting to set QLayout “” on Widget “Widget”, which already has a layout这个错误的出处正是这里。
其中
l->setParent(this);
l->d_func()->reparentChildWidgets(this);
说明了这一切。
QLayout继承自QObject,并指定了父对象this,满足对象树机制的条件。
至此第二个问题也很明了了,m_pHBoxLayout虽然是个栈对象,在它销毁之前通过l->d_func()->reparentChildWidgets(this);
将布局中的控件重新指定了父对象this。于是就出现了以上第二个例子的现象。
总结
采用布局管理的方式进行界面设计,不必手动delete。但是要注意,不满足对象树机制的对象,要谨防内存泄漏,老老实实地把它delete掉。