Are destructors necessary in QDialogs?

时间:2015-06-30 13:55:29

标签: c++ qt memory-leaks

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 ?

3 个答案:

答案 0 :(得分:4)

我认为你因为这些问题而感到困惑:

tabWidget = new QTabWidget;//and so on

你没有看到明确的父母(如new QTabWidget(this);),但这里没有必要。看看这里:

QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(tabWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);

setLayout将重新显示您的QVBoxLayoutQVBoxLayout会重新显示其中的所有小部件,因此现在您的小部件有一个父级,它们将在您的对话后被销毁。

正如doc所说:

  

使用布局时,不需要传递父级   构建子窗口小部件。布局将自动重新显示   小部件(使用QWidget :: setParent())使它们成为子类   安装布局的小部件。

     

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

答案 1 :(得分:2)

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

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

  1. 标签一旦传递到addTab,就会成为父级。
  2. tabWidgetbuttonBox一旦添加到布局中就会成为父级。
  3. 由于您在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的大小要大得多(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上的内存处理

Qt将小部件作为树处理,每个小部件都有父级,每个父级都有 释放儿童记忆的义务,如果一个小部件没有父母,你应该用操作员delete手动删除它。