QDialogs 中是否需要析构函数?
Are destructors necessary in QDialogs?
我正在关注 Qt 示例(如 TabDialog),我注意到所有 UI 项目都是作为指针创建的 - 但我没有看到 delete
也没有析构函数。
对吗?这不会导致内存泄漏吗?
我正在尝试添加析构函数
~TabDialog()
{
delete tabWidget;
delete buttonBox;
}
和来电者
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
但是当我关闭对话框时程序崩溃了。
析构函数和 delete
所有指针项都是不必要的还是我做错了?
我认为你是因为这些行而感到困惑:
tabWidget = new QTabWidget;//and so on
您看不到明确的父项(如 new QTabWidget(this);
),但这里没有必要。看这里:
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(tabWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setLayout
将重新设置您的 QVBoxLayout
并且 QVBoxLayout
将重新设置其中所有小部件的父级,所以现在您的小部件有一个父级,它们将在您的对话后被销毁。
正如doc所说:
When you use a layout, you do not need to pass a parent when
constructing the child widgets. The layout will automatically reparent
the widgets (using QWidget::setParent()) so that they are children of
the widget on which the layout is installed.
Note: Widgets in a layout are children of the widget on which the
layout is installed, not of the layout itself. Widgets can only have
other widgets as parent, not layouts.
Qt 上的内存处理
Qt 将小部件作为树来处理,每个小部件都有一个父对象,每个父对象都有
释放子项内存的义务,如果一个小部件没有父项,您应该使用运算符 delete
.
手动删除它
抱歉,这无法重现。测试用例如下。您可能需要添加一些额外的代码才能使其重现。
Qt 的内存管理会处理所有事情,因为所有的小部件最终都会有 parents。即:
- 选项卡在传递给
addTab
后立即成为父级。
tabWidget
和 buttonBox
在添加到布局后立即成为父级。
由于您在 Qt 尝试删除之前删除了 tabWidget
和 buttonBox
,所以一切都很好。一旦您删除它们,QObject
的内存管理就会收到通知,并将它们从 TabDialog
的 child 列表中删除。我已经在析构函数代码中明确指出了这一点。
Q_ASSERT
的意思是:"At this point during runtime, the following must be true"。如果我们错了,调试构建将中止。因为它没有,断言是正确的。因此,在delete tabWidget
之前,对话框有QTabWidget
和QDialogButtonBox
children。在 delete tabWidget
之后,对话框不应再有任何 QTabWidget
children。等等。
#include <QApplication>
#include <QDialog>
#include <QTabWidget>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class TabDialog : public QDialog
{
QTabWidget *tabWidget;
QDialogButtonBox *buttonBox;
public:
TabDialog() :
tabWidget(new QTabWidget),
buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel))
{
tabWidget->addTab(new QWidget, tr("General"));
tabWidget->addTab(new QWidget, tr("Permissions"));
tabWidget->addTab(new QWidget, tr("Applications"));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(tabWidget);
layout->addWidget(buttonBox);
setLayout(layout);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
}
~TabDialog() {
Q_ASSERT(findChild<QTabWidget*>());
Q_ASSERT(findChild<QDialogButtonBox*>());
delete tabWidget;
Q_ASSERT(! findChild<QTabWidget*>());
Q_ASSERT(findChild<QDialogButtonBox*>());
delete buttonBox;
Q_ASSERT(! findChild<QTabWidget*>());
Q_ASSERT(! findChild<QDialogButtonBox*>());
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
return 0;
}
它崩溃的唯一方法是您尝试以下操作:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
// At this point `tabDialog` is a dangling pointer.
delete tabDialog; // crash
return 0;
}
不幸的是,Qt 示例是毫无意义的过早悲观化的例子。 Qt 的 classes 广泛地使用了 PIMPL 习语。因此,QTabWidget
的大小并不比 QObject
大多少(在我的 64 位平台上是 48 字节和 16 字节)。通过在堆上分配 class 的固定成员,您正在执行 两次 堆分配:QObject
派生的 class,然后是另一个 PIMPL。您无缘无故地将分配数量加倍。
以下是避免这种悲观情绪的方法:
#include <QApplication>
#include <QDialog>
#include <QTabWidget>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class TabDialog : public QDialog
{
QVBoxLayout m_layout;
QTabWidget m_tabWidget;
QDialogButtonBox m_buttonBox;
QWidget m_generalTab, m_permissionsTab, m_applicationsTab;
public:
TabDialog() :
m_layout(this),
m_buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
{
m_tabWidget.addTab(&m_generalTab, tr("General"));
m_tabWidget.addTab(&m_permissionsTab, tr("Permissions"));
m_tabWidget.addTab(&m_applicationsTab, tr("Applications"));
m_layout.addWidget(&m_tabWidget);
m_layout.addWidget(&m_buttonBox);
connect(&m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(&m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
auto tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->show(); // NOT tabDialog->exec()!!
return app.exec();
}
堆分配越不明确越好。这样你就不会想 delete
析构函数中的任何东西,因为不涉及指针。编译器会自动为您生成必要的析构函数调用。
此外,如果您在 main
中只显示一个 window,那么显式堆分配也没有意义:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
TabDialog tabDialog;
tabDialog.show();
return app.exec();
}
我正在关注 Qt 示例(如 TabDialog),我注意到所有 UI 项目都是作为指针创建的 - 但我没有看到 delete
也没有析构函数。
对吗?这不会导致内存泄漏吗?
我正在尝试添加析构函数
~TabDialog()
{
delete tabWidget;
delete buttonBox;
}
和来电者
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
但是当我关闭对话框时程序崩溃了。
析构函数和 delete
所有指针项都是不必要的还是我做错了?
我认为你是因为这些行而感到困惑:
tabWidget = new QTabWidget;//and so on
您看不到明确的父项(如 new QTabWidget(this);
),但这里没有必要。看这里:
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(tabWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setLayout
将重新设置您的 QVBoxLayout
并且 QVBoxLayout
将重新设置其中所有小部件的父级,所以现在您的小部件有一个父级,它们将在您的对话后被销毁。
正如doc所说:
When you use a layout, you do not need to pass a parent when constructing the child widgets. The layout will automatically reparent the widgets (using QWidget::setParent()) so that they are children of the widget on which the layout is installed.
Note: Widgets in a layout are children of the widget on which the layout is installed, not of the layout itself. Widgets can only have other widgets as parent, not layouts.
Qt 上的内存处理
Qt 将小部件作为树来处理,每个小部件都有一个父对象,每个父对象都有
释放子项内存的义务,如果一个小部件没有父项,您应该使用运算符 delete
.
抱歉,这无法重现。测试用例如下。您可能需要添加一些额外的代码才能使其重现。
Qt 的内存管理会处理所有事情,因为所有的小部件最终都会有 parents。即:
- 选项卡在传递给
addTab
后立即成为父级。 tabWidget
和buttonBox
在添加到布局后立即成为父级。
由于您在 Qt 尝试删除之前删除了 tabWidget
和 buttonBox
,所以一切都很好。一旦您删除它们,QObject
的内存管理就会收到通知,并将它们从 TabDialog
的 child 列表中删除。我已经在析构函数代码中明确指出了这一点。
Q_ASSERT
的意思是:"At this point during runtime, the following must be true"。如果我们错了,调试构建将中止。因为它没有,断言是正确的。因此,在delete tabWidget
之前,对话框有QTabWidget
和QDialogButtonBox
children。在 delete tabWidget
之后,对话框不应再有任何 QTabWidget
children。等等。
#include <QApplication>
#include <QDialog>
#include <QTabWidget>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class TabDialog : public QDialog
{
QTabWidget *tabWidget;
QDialogButtonBox *buttonBox;
public:
TabDialog() :
tabWidget(new QTabWidget),
buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel))
{
tabWidget->addTab(new QWidget, tr("General"));
tabWidget->addTab(new QWidget, tr("Permissions"));
tabWidget->addTab(new QWidget, tr("Applications"));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(tabWidget);
layout->addWidget(buttonBox);
setLayout(layout);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
}
~TabDialog() {
Q_ASSERT(findChild<QTabWidget*>());
Q_ASSERT(findChild<QDialogButtonBox*>());
delete tabWidget;
Q_ASSERT(! findChild<QTabWidget*>());
Q_ASSERT(findChild<QDialogButtonBox*>());
delete buttonBox;
Q_ASSERT(! findChild<QTabWidget*>());
Q_ASSERT(! findChild<QDialogButtonBox*>());
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
return 0;
}
它崩溃的唯一方法是您尝试以下操作:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
// At this point `tabDialog` is a dangling pointer.
delete tabDialog; // crash
return 0;
}
不幸的是,Qt 示例是毫无意义的过早悲观化的例子。 Qt 的 classes 广泛地使用了 PIMPL 习语。因此,QTabWidget
的大小并不比 QObject
大多少(在我的 64 位平台上是 48 字节和 16 字节)。通过在堆上分配 class 的固定成员,您正在执行 两次 堆分配:QObject
派生的 class,然后是另一个 PIMPL。您无缘无故地将分配数量加倍。
以下是避免这种悲观情绪的方法:
#include <QApplication>
#include <QDialog>
#include <QTabWidget>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class TabDialog : public QDialog
{
QVBoxLayout m_layout;
QTabWidget m_tabWidget;
QDialogButtonBox m_buttonBox;
QWidget m_generalTab, m_permissionsTab, m_applicationsTab;
public:
TabDialog() :
m_layout(this),
m_buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
{
m_tabWidget.addTab(&m_generalTab, tr("General"));
m_tabWidget.addTab(&m_permissionsTab, tr("Permissions"));
m_tabWidget.addTab(&m_applicationsTab, tr("Applications"));
m_layout.addWidget(&m_tabWidget);
m_layout.addWidget(&m_buttonBox);
connect(&m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(&m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
auto tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->show(); // NOT tabDialog->exec()!!
return app.exec();
}
堆分配越不明确越好。这样你就不会想 delete
析构函数中的任何东西,因为不涉及指针。编译器会自动为您生成必要的析构函数调用。
此外,如果您在 main
中只显示一个 window,那么显式堆分配也没有意义:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
TabDialog tabDialog;
tabDialog.show();
return app.exec();
}