Qt - 如果一个在堆栈上有父级的QObject被删除两次会怎样?

6

请问以下问题的解释是什么?

栈或堆? 通常情况下,没有父对象的QObject应该被创建在栈上或定义为另一个类的子对象。有父对象的QObject不应该被创建在栈上,因为这样可能会意外地导致它被删除两次。所有在堆上创建的QObjects都应该有一个父对象,或者由另一个对象进行管理。

来源:LINK

1个回答

24

很抱歉,我要downvote接受的答案。目前两个版本都是错误的,尤其是结论非常错误。

父/子内存管理

Qt使用父/子概念来管理内存等内容。当一个对象被设置为另一个对象的子级时,则

  • 删除父级还将删除(通过operator delete)所有子级。 当然,这是递归进行的;
  • 删除子项将使其取消父子关系,以便父项不会尝试进行双重删除。 不需要使用deleteLater即可实现此操作——任何删除都将使其取消父子关系。

这使您可以通过重复使用operator new进行动态分配来构建QObject树,并且不必手动删除所有已分配的对象。只需给它们父级,您就只需要删除树的根节点。您也可以随时删除子项(即子树),并且这将做正确的事情™。

最终,您将没有任何泄漏和双重删除。

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

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分配了子对象,并且甚至没有保存指向某些子对象的指针。

堆栈上的QObjects

在某些情况下,允许(并且实际上是一个好主意)为在堆栈上分配的对象提供父对象。

典型的例子是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作为对话框的父级传递的目的是允许对话框居中于父部件,共享任务托盘条目并与其模态。这在QDialog文档中有解释。另外,最终,d只需要在该函数中存在,因此将其声明为自动变量(即分配在堆栈上)是一个好主意。
在那里:您已经有了一个在堆栈上分配了父级的QObject
危险的是,如果将QObject置于堆栈上,请考虑以下代码:
QObject *parent = new QObject;
QObject child(parent);
delete parent;

正如之前所解释的那样,在这里parent会试图调用operator delete来删除child,一个并非使用new(而是在栈上)分配的对象。这是非法的(也很可能导致崩溃)。
显然,没有人会编写这样的代码,但请再次考虑上面的对话示例。如果在调用d.exec()期间,我们设法删除了this,也就是对话框的父对象,该怎么办?这可能是由于各种非常难以跟踪的原因导致的——例如,数据通过套接字到达,导致UI中的小部件发生变化,创建一些并销毁其他一些。最终,您将删除堆栈变量,从而导致崩溃(并且试图重现崩溃可能会非常困难)。
因此,建议首先避免编写这样的代码。这不是非法的,它可能有效,但也可能无效,并且没有人喜欢脆弱的代码。

谢谢你的正确答案,peppe。我写得太快了,没有足够的咖啡因在我的身体里。我正在拍着我的额头。 - jonspaceharper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接