C++ Qt 如何删除对象?存储 QObjects 的最佳方式是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19331396/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How does Qt delete objects ? And what is the best way to store QObjects?
提问by Linlix
I heard that objects in Qt will automatically delete their children, I want to know what will happen in those situations.
我听说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();
}
Is this allowed in Qt? What does Qt do when destroying a child ?
这在 Qt 中是允许的吗?Qt在摧毁一个孩子时会做什么?
BTW, I considered using smart pointers such as shared_ptr. But I think Qt would also delete the object which had already been destroyed by smart pointer too.
顺便说一句,我考虑过使用诸如shared_ptr 之类的智能指针。但我认为 Qt 也会删除已经被智能指针破坏的对象。
I know you would like to use new to allocate dynamic memory for objects. But I don't feel its reassuring, please tell me if there are any situations (e.g. exceptions) that will lead to memory leaks when relying on Qt's object tree to handle dynamic memory?
我知道您想使用 new 为对象分配动态内存。但是我觉得不放心,请问在依赖Qt的对象树处理动态内存时,有没有什么情况(比如异常)会导致内存泄漏?
If I use objects rather than pointers to dynamically allocate objects, I have to consider the order of destruction of objects as long as they have ownership, which is tedious. I don't know whether it is good practice to use dynamic memory in Qt.
如果我使用对象而不是指针来动态分配对象,我必须考虑对象的销毁顺序,只要它们有所有权,这很繁琐。我不知道在 Qt 中使用动态内存是否是一个好习惯。
Do you have any suggestions or better solutions?
您有什么建议或更好的解决方案吗?
回答by RobbieE
The QObject
implementation of the Composite Design Patternhas been tried and tested through the many versions of Qt.
复合设计模式的QObject
实现已经通过 Qt 的许多版本进行了尝试和测试。
The pattern requires that the composite object takes ownership of the children so, as long as the parenting has been done, you can be assured that the child QObjects
will be destroyed when the parent is destroyed.
该模式要求复合对象拥有子对象的所有权,因此,只要QObjects
父级完成,您就可以放心,当父对象被销毁时,子对象也会被销毁。
Standard practice is to create child objects in heap memory and parent them immediately. If you don't parent immediately, you can explicitly parent using the setParent()
function, or else parenting will be done automatically when you add the widget to a parent widget, either using addWidget()
or addLayout()
.
标准做法是在堆内存中创建子对象并立即将它们作为父对象。如果您不立即作为父级,您可以使用该setParent()
函数显式作为父级,否则当您将小部件添加到父级小部件时,使用addWidget()
或将自动完成父级addLayout()
。
QLayout
objects are size and layout managers of other QLayouts
and of QWidgets
. They don't own the objects they manage. The parent is actually the QWidget
that the QLayout
is the child of.
QLayout
对象是 otherQLayouts
和 of 的大小和布局管理器QWidgets
。他们不拥有他们管理的对象。父对象实际上是QWidget
它QLayout
的子对象。
You have a choice to create the root parent in stack memory or in heap memory.
您可以选择在堆栈内存或堆内存中创建根父级。
If you feel more comfortable with smart pointers, there are two classes that are specifically for QObjects
: QPointerand QSharedPointer. Each has their pros and cons.
如果您对智能指针感觉更舒服,有两个类专门用于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
}
回答by Reinstate Monica
Adding to RobbiE's answer, the QPointerand QSharedPointerare two complemetary classes that serve different functions.
除了 RobbiE 的答案之外,QPointer和QSharedPointer是两个具有不同功能的互补类。
QPointer and its Caveats
QPointer 及其注意事项
A QPointer
is a weak pointer to a QObject
. It resets itself to zero when the pointed-to object is destroyed. It is not an owning pointer: it never deletes the object itself, and it doesn't guarantee the object's existence. Use it to avoid having a dangling pointer to an object whose ownership is managed elsewhere. Check whether the pointer is null before each use. You will run into race conditions if the object is destructed in another thread:
AQPointer
是指向 a 的弱指针QObject
。当指向的对象被销毁时,它会将自身重置为零。它不是一个拥有指针:它从不删除对象本身,也不保证对象的存在。使用它可以避免有一个指向所有权在别处管理的对象的悬空指针。每次使用前检查指针是否为空。如果对象在另一个线程中被破坏,您将遇到竞争条件:
if (pointer) /* another thread can destruct it here */ pointer->method();
The QPointer
itself is thread-safe, but the code that uses it cannot ever be thread-safe due to the insufficient API provided by QPointer
.
在QPointer
本身是线程安全的,但代码使用它永远不能是线程安全的,由于所提供的API不够QPointer
。
The QPointer
is always safe to use from the main thread with widget objects, and with the objects owned by widget objects where the parent-child relationship is established. The objects and their users are in the same thread, so the object will not be disposed by another thread between the pointer null check and the use of the pointer:
将QPointer
始终是安全从控件对象的主线程使用,并通过widget对象建立了父子关系,其中拥有的对象。对象和它们的用户在同一个线程中,因此在指针空检查和使用指针之间,对象不会被另一个线程处理:
QPointer<QLabel> label(...);
if (label) label->setText("I'm alive!");
You need to be careful if you're reentering the event loop. Suppose we have:
如果您重新进入事件循环,则需要小心。假设我们有:
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(...);
}
At the very least, you need to re-check the QPointer
every time after you invoke principally unrelated code - e.g. emit a signal (anyone can do anything in reaction to it!), return an event-loop-reentering call like exec
. etc. That's also why blocking calls are evil: you should never use them.
至少,您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 and QWeakPointer
QSharedPointer 和 QWeakPointer
This section is left as a reference. In modern code, you should be using std::shared_ptr
and std::weak_ptr
, without any reservations. They have been in C++ for 7 years as of 2018.
本节留作参考。在现代代码中,您应该毫无保留地使用std::shared_ptr
and std::weak_ptr
。到 2018 年,他们已经使用 C++ 7 年了。
A QSharedPointer
is an owning pointer. It works like variables in Java and CPython, or like std::shared_ptr
. As long as there is at least one QSharedPointer
pointing to an object, the object is kept around. When the last QSharedPointer
is destructed, the object gets destructed and deleted.
AQSharedPointer
是一个拥有指针。它像 Java 和 CPython 中的变量一样工作,或者像std::shared_ptr
. 只要至少有一个QSharedPointer
指向一个对象,该对象就被保留在周围。当最后一个QSharedPointer
被破坏时,对象被破坏和删除。
The QWeakPointer
is QSharedPointer
's cousin. It is non-owning. It tracks whether the objects held by QSharedPointer
s are still alive. It resets itself to nullptr
when the last QSharedPointer
that owns the object goes away. It can be thought of as a generalization of QPointer
to non-QObject
classes. The only safe way to use a QWeakPointer
is to convert it to a QSharedPointer
. When you hold a shared pointer, the object will be guaranteed to stay alive.
该QWeakPointer
是QSharedPointer
的堂弟。它是非拥有的。它跟踪QSharedPointer
s持有的对象是否还活着。nullptr
当最后QSharedPointer
一个拥有该对象的人消失时,它会自行重置。它可以被认为是QPointer
对非QObject
类的推广。使用 a 的唯一安全方法QWeakPointer
是将其转换为 a QSharedPointer
。当您持有共享指针时,将保证该对象保持活动状态。
A QPointer
is like a QWeakPointer
for QObject
s, but it doesn't require the existence of a QSharedPointer
.
AQPointer
类似于QWeakPointer
for QObject
s,但它不需要 a 的存在QSharedPointer
。
It is an error to use a QSharedPointer
on an object that's not allocated on the heap, and on an object whose lifetime is managed by other mechanisms. For example, it's an error to have a QSharedPointer
to a QObject
that has a parent. The object's parent would delete it, and you would end up with a dangling QSharedPointer
! Qt has some built-in checks that issue warnings when that happens, but by that time it's too late and undefined behavior has struck.
QSharedPointer
在未在堆上分配的对象以及生命周期由其他机制管理的对象上使用 a 是错误的。例如,将 aQSharedPointer
设为QObject
具有父代的a是错误的。对象的父对象会删除它,你最终会得到一个悬空的QSharedPointer
! Qt 有一些内置检查会在发生这种情况时发出警告,但到那时为时已晚,未定义的行为已经发生。
QScopedPointer
QScoped指针
This section is left as a reference. You should be using std::unique_ptr
, without any reservations. It has been in C++ for 7 years as of 2018.
本节留作参考。您应该std::unique_ptr
毫无保留地使用 。截至 2018 年,它已经在 C++ 中使用了 7 年。
QScopedPointer
, just like std::unique_ptr
, is a solely owning pointer. Its job is to delete the held object when it goes out of scope. The C++11 unique_ptr
's name is very apt: it isa unique pointer, in the sense that it's an error to try and copy such pointers. There is always only one QScopedPointer
that owns a given object, and it does not cooperate with other smart pointer types. You can fetch a raw pointer to underlying object by calling the data
method.
QScopedPointer
,就像std::unique_ptr
,是一个单独拥有的指针。它的工作是在超出范围时删除持有的对象。C++11unique_ptr
的名字非常贴切:它是一个唯一的指针,从某种意义上说,尝试复制这样的指针是错误的。总是只有一个QScopedPointer
拥有给定的对象,并且它不与其他智能指针类型合作。您可以通过调用该data
方法来获取指向底层对象的原始指针。
std::auto_ptr
std::auto_ptr
This pointer was an attempt at working around lack of move semantics in C++98/03. Due to its broken copy semantics, the use of this class should be treated as a bug. Use std::unique_ptr
or std::shared_ptr
- the former if it suffices that it be movable, the latter if several copies of it must coexist.
这个指针是为了解决 C++98/03 中缺乏移动语义的问题。由于其破坏的复制语义,此类的使用应被视为错误。使用std::unique_ptr
or std::shared_ptr
- 前者如果它可以移动就足够了,后者如果它的多个副本必须共存。