Qt 如何删除对象?存储 QObject 的最佳方式是啥? [复制]
Posted
技术标签:
【中文标题】Qt 如何删除对象?存储 QObject 的最佳方式是啥? [复制]【英文标题】:How does Qt delete objects ? And what is the best way to store QObjects? [duplicate]Qt 如何删除对象?存储 QObject 的最佳方式是什么? [复制] 【发布时间】:2013-10-12 06:47:25 【问题描述】:听说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 中使用动态内存是否是一种好习惯。
您有什么建议或更好的解决方案吗?
【问题讨论】:
如果 shared_ptr 删除了孩子,则通知父母,所以这不是问题。问题是反过来的,当父删除子 => 双重删除时,shared_ptr 不会注意到。 【参考方案1】:Composite Design Pattern 的QObject
实现已经通过许多 Qt 版本进行了尝试和测试。
该模式要求复合对象拥有子对象的所有权,因此,只要完成了父对象,您就可以放心,当父对象被销毁时,子对象QObjects
将被销毁。
标准做法是在堆内存中创建子对象并立即将它们作为父对象。如果您不立即设置父级,则可以使用setParent()
函数显式设置父级,否则当您将小部件添加到父级小部件时,父级将自动完成,使用addWidget()
或addLayout()
。
QLayout
对象是其他QLayouts
和QWidgets
的大小和布局管理器。他们不拥有他们管理的对象。父级实际上是QWidget
,QLayout
是其子级。
您可以选择在堆栈内存或堆内存中创建根父节点。
如果您对智能指针感觉更舒服,有两个类专门用于QObjects
:QPointer 和QSharedPointer。各有优缺点。
#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
【讨论】:
综合中:在栈中分配根QObjects;堆上的所有其他东西 @tlonuk 不是。当您可以按值保存对象时,堆分配可能是过早的悲观。QObject
s 作为值就好了;它们既不可移动也不可复制,但在其他方面没有任何特殊性。【参考方案2】:
除了 RobbiE 的回答之外,QPointer 和 QSharedPointer 是两个具有不同功能的互补类。
QPointer 及其注意事项
QPointer
是指向QObject
的弱指针。当指向的对象被销毁时,它会将自身重置为零。它不是拥有指针:它从不删除对象本身,也不保证对象的存在。使用它来避免有一个悬空指针指向其所有权在其他地方管理的对象。每次使用前检查指针是否为空。如果对象在另一个线程中被破坏,您将遇到竞争条件:
if (pointer) /* another thread can destruct it here */ pointer->method();
QPointer
本身是线程安全的,但由于QPointer
提供的 API 不足,使用它的代码永远不可能是线程安全的。
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_ptr
和std::weak_ptr
。截至 2018 年,他们已经使用 C++ 7 年了。
QSharedPointer
是一个拥有指针。它的工作方式类似于 Java 和 CPython 中的变量,或者类似于 std::shared_ptr
。只要至少有一个QSharedPointer
指向一个对象,该对象就会一直存在。当最后一个QSharedPointer
被破坏时,对象被破坏并删除。
QWeakPointer
是 QSharedPointer
的表弟。它是非拥有的。它跟踪QSharedPointer
s 持有的对象是否还活着。当拥有该对象的最后一个QSharedPointer
消失时,它会将自身重置为nullptr
。它可以被认为是QPointer
到非QObject
类的泛化。使用QWeakPointer
的唯一安全方法是将其转换为QSharedPointer
。当您持有共享指针时,该对象将保证保持活动状态。
QPointer
类似于QObject
s 的QWeakPointer
,但它不需要存在QSharedPointer
。
在未在堆上分配的对象以及生命周期由其他机制管理的对象上使用QSharedPointer
是错误的。例如,有一个 QSharedPointer
到一个有父级的 QObject
是错误的。对象的父对象会删除它,你最终会得到一个悬空的QSharedPointer
! Qt 有一些内置检查,当发生这种情况时会发出警告,但到那时为时已晚,未定义的行为已经发生。
QScopedPointer
本节留作参考。您应该毫无保留地使用std::unique_ptr
。截至 2018 年,它已经在 C++ 中使用了 7 年。
QScopedPointer
,就像std::unique_ptr
,是一个独占指针。它的工作是在超出范围时删除持有的对象。 C++11 unique_ptr
的名字非常贴切:它是一个唯一的指针,从某种意义上说,试图复制这样的指针是错误的。总是只有一个QScopedPointer
拥有一个给定的对象,并且它不与其他智能指针类型合作。您可以通过调用data
方法获取指向底层对象的原始指针。
std::auto_ptr
这个指针试图解决 C++98/03 中缺少移动语义的问题。由于其破坏的复制语义,应将此类的使用视为错误。使用std::unique_ptr
或std::shared_ptr
- 前者如果它可以移动就足够了,后者如果它的多个副本必须共存。
【讨论】:
以上是关于Qt 如何删除对象?存储 QObject 的最佳方式是啥? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
Qt文档阅读笔记-void QObject::deleteLater()解析