Qt:编写此 class 的析构函数的正确且安全的方法是什么?

Qt: What is the correct and safe way to write the destructor of this class?

我在 Windows7 上使用 Qt5,我最近发现 an interesting Qt example code

基本上是这样的:

ButtonWidget::ButtonWidget(const QStringList &texts, QWidget * parent) 
: QWidget(parent)
{
    signalMapper = new QSignalMapper(this);

    QGridLayout * gridLayout = new QGridLayout;
    for (int i = 0; i < texts.size(); ++i) 
    {
        QPushButton * button = new QPushButton(texts[i]);
        connect(button, SIGNAL(clicked()), signalMapper, SLOT(map()));
        signalMapper->setMapping(button, texts[i]);
        gridLayout->addWidget(button, i / 3, i % 3);
    }

    connect(signalMapper, SIGNAL(mapped(QString)), this, SIGNAL(clicked(QString)));

    setLayout(gridLayout);
}

这是一个不错且有用的示例,但它没有合适的析构函数...以防万一我想删除 ButtonWidget 类型的对象,或者如果我想自定义代码能够 remove/add 小部件。这个想法是如何删除在构造函数中创建的所有对象(动态地,使用 new)。

我的方法是使用私有变量QList<QPushButton*> list,将所有新分配的按钮添加到列表(在构造函数中)并在析构函数中逐个删除它们,使用上面的list.但这似乎是幼儿园的做法。

我认为一定有其他更好的方法,无需列表,也不会弄乱构造函数代码:)感谢您的时间和耐心!

对于接收 parent 作为其构造函数参数的 Qt classes,例如 QSignalMapper,重要的是要注意 class 将自身添加到其 parent的 object 列表,当它的 parent (QObject)被销毁时,它将被销毁。

因此,如果您传递 object 和 parent,您无需执行任何操作。你的 cpp 文件中可能有一个空的析构函数,只是为了确保成员的定义对 QObject 可用,但这取决于 class.

但是,如果您确实选择编写自己的析构函数,则正确实现的典型 Qt "child" 会在销毁时从其 parent 中删除自身。

除了 caption/title 中提出的问题外,OP 询问如果他想在删除 parent 之前删除 sub-object 会发生什么:

看到 following,似乎(这是 iaw。我自己的经验)他可能只是删除了 child 小部件,并且它们会自行删除。已经提到可以将 child 的 parent 设置为空,但这不是必需的,除非有人想删除 parent 并保留 child 活着 (re-parenting...).

但是,如example最后一段所示,实例化的顺序很重要。如果 parent 在 child 之后实例化,并且 child 的 parent 被显式设置,则指向 parent 的无效引用/无效指针将是留下 child,当 child 试图从 out-of-scope parent.

中删除自身时,将在破坏 child 时发生未定义的行为

除了Qt的documentation, one can also look at the implementation of QObject::setParent here (setParent_helper)。从这里可以看出,除了提到的情况外,他们付出了很大的努力来确保 children/parents 删除不会发生任何意外。

来自QWidget::setLayout

The QWidget will take ownership of layout.

来自 QLayout::addItem(由 QLayout::addWidget 调用):

Note: The ownership of item is transferred to the layout, and it's the layout's responsibility to delete it.


您不必清理任何东西。通过布局管理小部件
(addWidget, removeWidget/removeItem).

您必须确保将所有小部件连接到 Qt 的 object tree,然后它将为您处理销毁。你可以通过在构造它时给它一个父对象来做到这一点。就像您对 SignalMapper.

所做的一样

没有 "proper" 析构函数是不正确的。 一个析构函数 - 一个由编译器生成的析构函数 - 并且该析构函数执行释放资源所需的一切。这就是它应该的样子,这就是现代 C++ 代码应该设计的样子。

正确设计的 C++ classes 应该可以使用,而无需显式管理它们的资源。就是这样。

此外,该示例不必要地动态分配可能只是 class 成员的成员。在 C++11 中也不需要信号映射器。我会这样做:

class ButtonWidget : public QWidget {
  Q_OBJECT
  QGridLayout m_layout { this };
public:
  ButtonWidget(const QStringList &items, QWidget *parent = 0);
  ~ButtonWidget();
  Q_SIGNAL void buttonClicked(const QString &);
}

ButtonWidget::ButtonWidget(const QStringList &items, QWidget *parent) 
: QWidget(parent)
{
  const int columns = 3;
  for (int i = 0; i < items.size(); ++i) {
    auto text = items[i];
    auto button = new QPushButton(text);
    connect(button, &QPushButton::clicked, [this, text]{
      emit buttonClicked(text);
    });
    m_layout.addWidget(button, i / columns, i % columns);
  }
}

ButtonWidget::~ButtonWidget() {}

这是一个完整、可用的小部件,没有内存泄漏。这就是现代 C++/Qt 应该有的样子。如果您需要在析构函数中做一些奇特的事情,您应该始终考虑将内存管理分解到它自己的 RAII class 中。例如,与其在析构函数中手动关闭文件句柄,不如考虑使用 QFile,或编写类似的资源管理 class,然后您可以使用它而不必担心手动管理文件的生命周期手柄。