我听说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在摧毁孩子时做了什么?
BTW,我考虑过使用智能指针,比如shared_ptr。但我认为Qt也会删除已被智能指针销毁的对象。我知道您想使用new为对象分配动态内存。但我不觉得它让人放心,请告诉我是否有任何情况(例如异常)在依赖Qt的对象树处理动态内存时会导致内存泄漏?
如果我使用对象而不是指针来动态分配对象,我必须考虑对象的破坏顺序,只要它们具有所有权,这是乏味的。 我不知道在Qt中使用动态内存是否是一种好习惯。
您有任何建议或更好的解决方案吗?
答案 0 :(得分:26)
Composite Design Pattern的QObject
实施已经过Qt的许多版本的尝试和测试。
模式要求复合对象获得子项的所有权,因此,只要父项已经完成,就可以确保在父项被销毁时子项QObjects
将被销毁。
标准做法是在堆内存中创建子对象并立即对它们进行父对象。如果您不立即使用父项,则可以使用setParent()
函数显式生成父项,否则当您使用addWidget()
或{{1}将小组件添加到父小组件时,将自动完成父项操作}。
addLayout()
个对象是其他QLayout
和QLayouts
的大小和布局管理器。他们不拥有他们管理的对象。父亲实际上是QWidgets
QWidget
的孩子。
您可以选择在堆栈内存或堆内存中创建根父级。
如果您对智能指针感觉更舒服,则有两个专门针对QLayout
的类:QPointer和QSharedPointer。每个都有其优点和缺点。
QObjects
答案 1 :(得分:18)
添加到RobbiE的答案中,QPointer和QSharedPointer是两个用于不同功能的互补类。
QPointer
是指向QObject
的弱指针。当指向对象被销毁时,它会将自身重置为零。它不是一个拥有的指针:它永远不会删除对象本身,也不保证对象的存在。使用它来避免悬挂指向其他所有权管理对象的指针。每次使用前检查指针是否为空。如果对象在另一个线程中被破坏,您将遇到竞争条件:
if (pointer) /* another thread can destruct it here */ pointer->method();
QPointer
本身是线程安全的,但由于QPointer
提供的API不足,使用它的代码永远不会是线程安全的。
QPointer
始终可以安全地使用带有窗口小部件对象的主线程,以及建立父子关系的窗口小部件对象所拥有的对象。对象及其用户位于同一个线程中,因此对象不会被指针null检查和指针使用之间的另一个线程处理:
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(...);
...
}
此部分作为参考。在现代代码中,您应该使用std::shared_ptr
和std::weak_ptr
,而不做任何保留。截至2018年,他们已经在C ++工作了7年。
QSharedPointer
是拥有指针。它的工作方式类似于Java和CPython中的变量,或类似std::shared_ptr
。只要至少有一个QSharedPointer
指向一个对象,该对象就会被保留。当最后一个QSharedPointer
被破坏时,该对象被破坏并被删除。
QWeakPointer
是QSharedPointer
的堂兄。它是非拥有的。它跟踪QSharedPointer
所持有的对象是否仍然存在。当拥有该对象的最后一个nullptr
消失时,它会将自身重置为QSharedPointer
。它可以被视为QPointer
到非QObject
类的概括。使用QWeakPointer
的唯一安全方法是将其转换为QSharedPointer
。当您持有共享指针时,该对象将保证保持活跃状态。
QPointer
就像QWeakPointer
的{{1}}一样,但它不需要QObject
的存在。
对未在堆上分配的对象以及其生命周期由其他机制管理的对象使用QSharedPointer
是错误的。例如,将QSharedPointer
设置为具有父级的QSharedPointer
是错误的。对象的父对象会将其删除,最终会有一个悬空QObject
! Qt有一些内置的检查,当发生这种情况时会发出警告,但到那时为止已经太晚了,未定义的行为已经发生。
此部分作为参考。您应该使用QSharedPointer
,没有任何保留。截至2018年,它已在C ++中使用了7年。
std::unique_ptr
,就像QScopedPointer
一样,是一个独立的指针。它的工作是在超出范围时删除被保持的对象。 C ++ 11 std::unique_ptr
的名称很贴切:它是一个唯一的指针,在某种意义上说,尝试复制这些指针是一个错误。始终只有一个unique_ptr
拥有给定对象,并且它不与其他智能指针类型配合。您可以通过调用QScopedPointer
方法获取指向底层对象的原始指针。
这个指针试图解决C ++ 98/03中缺少移动语义的问题。由于其复制语义损坏,应将此类的使用视为错误。使用data
或std::unique_ptr
- 前者如果它可以移动就足够了,后者如果它的几个副本必须共存。