I am following Qt examples (like TabDialog) and I notice that all the UI items are created as pointers - yet I see no delete
and no destructor.
Is that right ? Will that not lead to memory leak ?
I am trying to add destructors
~TabDialog()
{
delete tabWidget;
delete buttonBox;
}
and on caller
TabDialog *tabDialog = new TabDialog();
tabDialog->setAttribute(Qt::WA_DeleteOnClose);
tabDialog->exec();
But the program crashes when I close the dialog.
Are the destructors, and delete
all pointer items, unnecessary or am I doing it wrong ?
答案 0 :(得分:4)
我认为你因为这些问题而感到困惑:
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所说:
使用布局时,不需要传递父级 构建子窗口小部件。布局将自动重新显示 小部件(使用QWidget :: setParent())使它们成为子类 安装布局的小部件。
注意:布局中的窗口小部件是窗口小部件的子窗口 布局是安装的,而不是布局本身。小部件只能有 其他小部件作为父级,而不是布局。
答案 1 :(得分:2)
对不起,但这不会重现。测试用例如下。您可能需要添加一些额外的代码才能使其重现。
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
的大小要大得多(48位平台上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();
}
答案 2 :(得分:1)
Qt将小部件作为树处理,每个小部件都有父级,每个父级都有
释放儿童记忆的义务,如果一个小部件没有父母,你应该用操作员delete
手动删除它。