Qt如何删除对象?什么是存储QObjects的最佳方式?

Lin*_*lix 14 c++ qt destruction

我听说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();
}
Run Code Online (Sandbox Code Playgroud)

Qt允许这样做吗?Qt在摧毁孩子时做了什么?

顺便说一下,我考虑过使用智能指针,比如shared_ptr.但我认为Qt也会删除已被智能指针销毁的对象.

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

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

您有任何建议或更好的解决方案吗?

Rob*_*ieE 28

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

该模式要求复合对象获得子项的所有权,因此,只要QObjects父项已经完成,就可以确保在父项被销毁时子项将被销毁.

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

QLayout对象是其他大小和布局管理器QLayoutsQWidgets.他们不拥有他们管理的对象.家长实际上是QWidgetQLayout是孩子.

您可以选择在堆栈内存或堆内存中创建根父级.

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

#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
}
Run Code Online (Sandbox Code Playgroud)

  • 在综合中:在堆栈中分配根QObject; 堆上的其他一切 (4认同)
  • @tlonuk 不是。当您可以按值保存对象时,堆分配可能是一种过早的悲观化。`QObject` 作为值就可以了;它们不可移动或复制,但除此之外没有任何特别之处。 (2认同)

Rei*_*ica 19

除了RobbiE的答案之外,QPointerQSharedPointer是两个可以提供不同功能的互补类.

QPointer及其警告

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

if (pointer) /* another thread can destruct it here */ pointer->method();
Run Code Online (Sandbox Code Playgroud)

QPointer本身是线程安全的,但由于API提供的API不足,使用它的代码永远不能是线程安全的QPointer.

QPointer始终是安全从控件对象的主线程使用,并与在那里建立了父子关系widget对象拥有的对象.对象及其用户位于同一个线程中,因此对象不会被指针null检查和指针使用之间的另一个线程处理:

QPointer<QLabel> label(...);
if (label) label->setText("I'm alive!");
Run Code Online (Sandbox Code Playgroud)

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

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(...);
}
Run Code Online (Sandbox Code Playgroud)

至少,你需要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(...);
  ...
}
Run Code Online (Sandbox Code Playgroud)

QSharedPointer和QWeakPointer

本节留待作为参考.在现代代码中,您应该使用std::shared_ptr并且std::weak_ptr没有任何保留.截至2018年,他们已经在C++工作了7年.

A QSharedPointer是拥有指针.它像Java和CPython中的变量一样工作std::shared_ptr.只要至少有一个QSharedPointer指向一个对象,该对象就会被保留.当最后一个QSharedPointer被破坏时,对象被破坏并被删除.

QWeakPointerQSharedPointer堂兄.它是非拥有的.它跟踪QSharedPointers 持有的对象是否仍然存活.它将自身重置为拥有该对象nullptr的最后一个QSharedPointer消失的时间.它可以被认为是QPointer对非QObject类的概括.使用a的唯一安全方法QWeakPointer是将其转换为QSharedPointer.当您持有共享指针时,该对象将保证保持活跃状态​​.

一个QPointer是像一个QWeakPointerQObjectS,但它并不需要的存在QSharedPointer.

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

QScopedPointer

本节留待作为参考.您应该使用std::unique_ptr,没有任何保留.截至2018年,它已在C++中使用了7年.

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

性病:: auto_ptr的

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