在QDialogs中是否需要析构函数

Are destructors necessary in QDialogs?

本文关键字:析构函数 是否 QDialogs      更新时间:2023-10-16

我正在使用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将修复你的QVBoxLayoutQVBoxLayout将修复其中的所有小部件,所以现在你的小部件有了一个父级,它们将在你的对话框后被销毁。

正如医生所说:

当您使用布局时,当构造子窗口小部件。布局将自动修复小部件(使用QWidget::setParent()),以便它们是安装布局的小部件。

注意:布局中的小部件是小部件的子部件布局是安装的,而不是布局本身。小工具只能其他小部件作为父部件,而不是布局。

很抱歉,但这不会重现。测试用例如下。您可能需要添加一些额外的代码才能使其重现。

Qt的内存管理将处理所有的事情,因为所有的小部件最终都有了父母。即:

  1. 选项卡在传递给addTab后立即成为父选项卡
  2. CCD_ 8和CCD_

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

Q_ASSERT的含义是:"在运行时的这一点上,以下内容必须为真"。如果我们错了,调试构建将中止。既然没有,那么断言是正确的。因此,在delete tabWidget之前,对话框同时具有QTabWidgetQDialogButtonBox子级。在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手动删除它。