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。即:

  1. 选项卡在传递给 addTab 后立即成为父级。
  2. tabWidgetbuttonBox 在添加到布局后立即成为父级。

由于您在 Qt 尝试删除之前删除了 tabWidgetbuttonBox,所以一切都很好。一旦您删除它们,QObject 的内存管理就会收到通知,并将它们从 TabDialog 的 child 列表中删除。我已经在析构函数代码中明确指出了这一点。

Q_ASSERT的意思是:"At this point during runtime, the following must be true"。如果我们错了,调试构建将中止。因为它没有,断言是正确的。因此,在delete tabWidget之前,对话框有QTabWidgetQDialogButtonBoxchildren。在 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();
}