20

我听说Qt中的对象会自动删除他们的孩子,我想知道在那些情况下会发生什么。

#include <QApplication>
#include <QLabel>
#include <QHBoxLayout>
#include <QWidget>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
/*
    QLabel label("label");      //  Program will crash. Destruct order is 1. widget, 2. layout, 3. label
    QHBoxLayout layout;         //  But layout will be deleted twice
    QWidget widget;
*/
    QWidget widget;             //  Program doesn't seem to crash but is it safe ? Does Qt use
    QHBoxLayout layout;         //  delete to operate on already destructed children ?
    QLabel label("label");

    layout.addWidget(&label);   //  layout is label's parent
    widget.setLayout(&layout);  //  widget is layout's parent
    widget.show();
    return app.exec();
}

这在 Qt 中是否允许?Qt 在销毁孩子时会做什么?

顺便说一句,我考虑使用诸如 shared_ptr 之类的智能指针。但我认为Qt也会删除已经被智能指针破坏的对象。

我知道您想使用 new 为对象分配动态内存。但是我不放心,请告诉我在依赖Qt的对象树处理动态内存时,是否有任何情况(例如异常)会导致内存泄漏?

如果我使用对象而不是指针来动态分配对象,我必须考虑对象的销毁顺序,只要它们具有所有权,这很繁琐。我不知道在 Qt 中使用动态内存是否是一种好习惯。

您有什么建议或更好的解决方案吗?

4

2 回答 2

39

复合设计模式QObject实现已经通过 Qt 的许多版本进行了尝试和测试。

该模式要求复合对象拥有子对象的所有权,因此,只要完成了父对象,您就可以放心,QObjects当父对象被销毁时,子对象也会被销毁。

标准做法是在堆内存中创建子对象并立即将它们作为父对象。如果您不立即作为父级,则可以使用该setParent()函数显式父级,否则当您将小部件添加到父级小部件时,父级将自动完成,使用addWidget()addLayout()

QLayout对象是 otherQLayouts和 的大小和布局管理器QWidgets。他们不拥有他们管理的对象。父母实际上是QWidget那个QLayout是孩子的。

您可以选择在堆栈内存或堆内存中创建根父节点。

如果您对智能指针感觉更舒服,则有两个专门用于QObjectsQPointerQSharedPointer的类。每个都有其优点和缺点。

#include <QApplication>
#include <QLabel>
#include <QHBoxLayout>
#include <QWidget>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QWidget widget;             // Root parent so can create as a auto-deleting object on the stack
    QHBoxLayout *layout = new QHBoxLayout(&widget);         // Create on the heap and parent immediately
    QLabel *label = new QLabel("label", &widget);           // Create on the heap and parent immediately

    layout->addWidget(label);   // widget remains label's parent
    widget.setLayout(layout);   // widget is changed to layout's parent if necessary, as well 
                                // as any widgets that layout manages
    widget.show();
    return app.exec();

    // layout and label are destroyed when widget is destroyed
}
于 2013-10-12T08:34:00.620 回答
23

添加到 RobbiE 的答案中,QPointerQSharedPointer是两个互补的类,它们提供不同的功能。

QPointer 及其注意事项

AQPointer是指向 a 的弱指针QObject。当指向的对象被销毁时,它会将自身重置为零。它不是拥有指针:它从不删除对象本身,也不保证对象的存在。使用它来避免有一个悬空指针指向其所有权在其他地方管理的对象。每次使用前检查指针是否为空。如果对象在另一个线程中被破坏,您将遇到竞争条件:

if (pointer) /* another thread can destruct it here */ pointer->method();

QPointer本身是线程安全的,但使用它的代码永远不可能是线程安全的,因为QPointer.

从主QPointer线程与小部件对象以及建立父子关系的小部件对象所拥有的对象一起使用始终是安全的。对象和它们的用户在同一个线程中,所以在指针空检查和指针使用之间对象不会被另一个线程释放:

QPointer<QLabel> label(...);
if (label) label->setText("I'm alive!");

如果您重新进入事件循环,您需要小心。假设我们有:

QPointer<QLabel> label(...);
...
if (label) {
   label->setText(...)
   QFileDialog::getOpenFileName(...);
  // Here the event loop is reentered, and essentially any other code in your
  // application can run, including code that could destruct the widget that
  // you're using. The `deleteLater` calls won't do it, since they defer to
  // the main event loop, but it's not always obvious that nothing else
  // will. The line below can thus dereference a null pointer (IOW: crash). 
  label->setText(...);
}

至少,您需要在QPointer每次调用主要不相关的代码后重新检查 - 例如发出一个信号(任何人都可以对它做任何反应!),返回一个事件循环重新进入调用,如exec. 等等。这也是为什么阻塞调用是邪恶的:你永远不应该使用它们。

QPointer<QWidget> widget(...);
...
if (label) {
  label->setText(...);
  QFileDialog::getOpenFileName(...);
  // Reenters the event loop, the widget may get deleted.
}
// Not re-checking the pointer here would be a bug.
if (label) {
  label->setText(...);
  ...
}

QSharedPointer 和 QWeakPointer

本节留作参考。在现代代码中,您应该毫无保留地使用std::shared_ptrand 。std::weak_ptr截至 2018 年,他们已经使用 C++ 7 年了。

AQSharedPointer是一个拥有指针。它的工作方式类似于 Java 和 CPython 中的变量,或者类似于std::shared_ptr. 只要至少有一个QSharedPointer指向一个对象,该对象就会一直存在。当最后一个QSharedPointer被破坏时,对象被破坏并删除。

QWeakPointer是的QSharedPointer表弟。它是非拥有的。它跟踪 s 持有的对象是否QSharedPointer还活着。nullptr当最后QSharedPointer一个拥有该对象的人离开时,它会自行重置。它可以被认为是QPointer对非QObject类的概括。使用 a 的唯一安全方法QWeakPointer是将其转换为QSharedPointer. 当您持有共享指针时,该对象将保证保持活动状态。

AQPointer类似于 a QWeakPointerfor QObjects,但它不需要 a 的存在QSharedPointer

QSharedPointer在未在堆上分配的对象上以及在其生命周期由其他机制管理的对象上使用 a 是错误的。例如,拥有父级QSharedPointer的 a是错误的。QObject对象的父级会删除它,你会得到一个悬空的QSharedPointer! Qt 有一些内置检查,当发生这种情况时会发出警告,但到那时为时已晚,未定义的行为已经发生。

QScoped指针

本节留作参考。您应该std::unique_ptr毫无保留地使用 . 截至 2018 年,它已经在 C++ 中使用了 7 年。

QScopedPointer, 就像std::unique_ptr, 是一个单独拥有的指针。它的工作是在超出范围时删除持有的对象。C++11unique_ptr的名字非常贴切:它一个唯一指针,从某种意义上说,尝试复制此类指针是错误的。始终只有一个QScopedPointer拥有给定对象,并且它不与其他智能指针类型合作。您可以通过调用该data方法来获取指向底层对象的原始指针。

标准::auto_ptr

这个指针试图解决 C++98/03 中缺少移动语义的问题。由于其破坏的复制语义,应将此类的使用视为错误。使用std::unique_ptror std::shared_ptr- 如果它足以移动,则使用前者,如果它的多个副本必须共存,则使用后者。

于 2013-10-13T15:40:43.537 回答