在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
将修复其中的所有小部件,所以现在你的小部件有了一个父级,它们将在你的对话框后被销毁。
正如医生所说:
当您使用布局时,当构造子窗口小部件。布局将自动修复小部件(使用QWidget::setParent()),以便它们是安装布局的小部件。
注意:布局中的小部件是小部件的子部件布局是安装的,而不是布局本身。小工具只能其他小部件作为父部件,而不是布局。
很抱歉,但这不会重现。测试用例如下。您可能需要添加一些额外的代码才能使其重现。
Qt的内存管理将处理所有的事情,因为所有的小部件最终都有了父母。即:
- 选项卡在传递给
addTab
后立即成为父选项卡 - CCD_ 8和CCD_
由于您在Qt尝试删除tabWidget
和buttonBox
之前就删除了它们,所以一切都很好。一旦删除它们,就会通知QObject
的内存管理,并将它们从TabDialog
的子列表中删除。我已经在析构函数代码中明确指出了这一点。
Q_ASSERT
的含义是:"在运行时的这一点上,以下内容必须为真"。如果我们错了,调试构建将中止。既然没有,那么断言是正确的。因此,在delete tabWidget
之前,对话框同时具有QTabWidget
和QDialogButtonBox
子级。在delete tabWidget
之后,对话框不应再具有任何QTabWidget
子级。等等。
#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的类广泛使用PIMPL习语。因此,比如说QTabWidget
的大小并不比QObject
大多少(在我的64位平台上是48字节对16字节)。通过在堆上分配类的固定成员,您将执行两个堆分配:一个小的用于QObject
派生类,然后另一个用于其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
中只显示一个窗口,那么显式堆分配也没有意义:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
TabDialog tabDialog;
tabDialog.show();
return app.exec();
}
Qt上的内存处理
Qt将小部件作为一个树来处理,每个小部件都有一个父级,每个父级都有释放子级内存的义务,如果小部件没有父级,则应使用操作员delete
手动删除它。
- 析构函数是否会自动调用 delete[] C++?
- 循环中本地对象的析构函数是否保证在下一次迭代之前被调用?
- 文件流析构函数是否可以在C++中引发异常?
- 构造函数是否unique_ptr初始化原始指针unique_ptr析构函数是否也删除关联的原始指针?
- 析构函数是否可以在 const 对象上调用非 const 函数
- cpp 中的析构函数是否自动调用?即使析构函数没有提及非动态变量,它们也会被删除吗?
- 析构函数是否可以不调用特定字段的析构函数
- 动态分配(堆上)的对象的析构函数是否会在返回函数中自动调用
- 当类中存在虚函数时,隐式生成的析构函数是否也是虚拟的
- C++析构函数是否始终或仅在有时调用数据成员析构函数
- 根据C++标准,显式调用构造函数和析构函数是否安全
- std:map 析构函数是否调用键析构函数以及值析构函数?
- QNetworkAccessManager 析构函数是否中止当前请求
- 基类析构函数是否阻止生成移动构造函数
- 琐碎的析构函数是否会导致混叠
- 全局变量构造函数/析构函数是否需要线程保护
- 多态类的隐式析构函数是否可以成为虚拟的
- 默认的虚拟析构函数是否阻止编译器生成的移动操作
- 析构函数是否自动为成员变量解除分配堆内存
- 对象的析构函数是否释放用于创建对象的指针所指向的内存?