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

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

本文关键字:删除 两次 QObject 堆栈 Qt      更新时间:2023-10-16

有人可以解释以下内容吗?

堆叠还是堆? 通常,应该在堆栈上创建没有父级的 QObject 或定义为另一个类的子对象。具有父级的 QObject 不应在堆栈上,因为这样它可能会意外删除两次。在堆上创建的所有 QObject 都应该有一个父对象,或者由另一个对象以某种方式管理。

来源:链接

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

用于内存管理的父/子项

除此之外,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分配了子对象,我们甚至懒得在成员中保存指向某些子对象的指针。

堆栈上的 Q

您可以(在某些情况下,这实际上是一个好主意)为堆栈上分配的对象提供父级。

典型的例子是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这里将尝试在child上调用operator delete,一个使用new分配的对象(它在堆栈上)。这是非法的(并且可能崩溃)。

显然,没有人像这样编写代码,但再次考虑上面的对话框示例。如果在调用d.exec()期间,我们设法以某种方式删除this,即对话框的父级怎么办?这可能由于各种原因而发生,非常非常难以追踪 - 例如,数据到达套接字,导致UI中的小部件发生变化,创建一些并破坏其他小部件。最终,您将删除堆栈变量,崩溃(并且很难尝试重现崩溃)。

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