对不起,但这只是无法重现。测试用例如下。您可能需要添加一些额外的代码才能使其重现。
Qt 的内存管理会处理所有事情,因为所有的小部件最终都有父母。即:
- 标签一旦被传递给
addTab,就会成为父标签。
-
tabWidget 和 buttonBox 在添加到布局后立即成为父级。
由于您在 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();
}