Qt - 堆栈上有父级的 QObject 如何被删除两次?

Qt - How a QObject with a parent on the stack might get deleted twice?

有人可以解释一下吗?

Stack or Heap? In general, a QObject without a parent should be created on the stack or defined as an subobject of another class. A QObject with a parent should not be on the stack because then it might get deleted twice accidentally. All QObjects created on the heap should either have a parent, or be managed somehow by another object.

来源:LINK

抱歉,对已接受的答案投反对票。目前解释的两个版本都是错误的,尤其是结论是非常错误的。

Parent/child 用于内存管理

除其他外,Qt 使用 parent/child 概念来管理内存。当一个 object 被 parented 到另一个时,那么

  • 删除 parent 也会删除(通过 operator delete)它的所有 children。 当然,这是递归的;
  • 删除 child 会 un-parent 它,这样 parent 就不会尝试双重删除。 deleteLater这不是工作所必需的——任何删除都会un-parent它。

这允许您通过 operator new 重复动态分配来构建 QObject 的树,而不必手动删除所有分配的 object 的问题。只要给他们 parents,你就只需要删除树的根。您还可以随时删除 child(即子树),这将做正确的事™。

最后,你将无漏无双删。

这就是为什么在构造函数中您会看到类似以下内容的原因:

class MyWidget : public QWidget // a QObject subclass
{
    Q_OBJECT
public:
    MyWidget(QWidget *parent = nullptr);

    // default destructor is fine!

private:
    // raw pointers:
    // we won't own these objects through these pointers.
    // we just need them to access the pointees
    QTimer *m_timer;
    QPushButton *m_button;
};

void MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
    // don't need to save the pointer to this child. because reasons
    auto lineEdit = new QLineEdit(this);
    auto validator = new QIntValidator(lineEdit); // a nephew

    // but let's save the pointers to these children
    m_timer = new QTimer(this);
    m_button = new QPushButton(this);
    // ...
}

默认析构函数将正确删除整棵树,尽管我们通过调用 operator new 分配了 children object,我们甚至懒得保存成员指向一些 children.

堆栈中的 QObjects

您可以(在某些情况下,这实际上是个好主意)将 parent 分配给分配在堆栈上的 object。

典型的例子是 QDialog 个子类:

void MyWidget::showOptionsDialog()
{
    // OptionsDialog is a QDialog subclass;
    // create an instance as a child of "this" object
    OptionsDialog d(this);

    // exec the dialog (i.e. show it as a modal dialog)
    conts auto result = d.exec();

    if (result == QDialog::Accept) {
        // apply the options
    }

    // d gets destroyed here 
    // => it will remove itself as a child of this
}

this作为对话框的parent传递的目的是让对话框以parent的小部件为中心,共享任务托盘条目,并对其进行模态处理。 这在 QDialog 文档中进行了解释。此外,最终 d 只需要存在于该函数中,因此将其声明为自动变量(即分配在堆栈上)是个好主意。

给你:你有一个 QObject,分配在堆栈上,parent。


那么堆栈上 QObject 的危险是什么?考虑这段代码:

QObject *parent = new QObject;
QObject child(parent);
delete parent;

如前所述,parent 将尝试在 child 上调用 operator delete,而 object 是 而不是 使用 new 分配(它在堆栈上)。这是非法的(并且可能会崩溃)。

显然,没有人会这样写代码,但请再考虑一下上面的对话框示例。如果在调用 d.exec() 期间,我们设法以某种方式删除了 this,即对话框的 parent,会怎样?发生这种情况的原因有很多,非常非常难以追踪——例如,数据到达套接字会导致 UI 中的小部件发生变化,创建一些小部件并破坏其他小部件。最终,你会删除一个堆栈变量,崩溃(并且很难重现崩溃)。

因此建议避免 首先创建这样的代码。这不违法,它可能有效,但也可能无效,没有人喜欢脆弱的代码。