How does Qt delete objects ? And what is the best

2019-01-17 08:35发布

问题:

This question already has an answer here:

  • Creating and deallocating a Qt widget object 2 answers

I heard that objects in Qt will automatically delete their children, I want to know what will happen in those situations.

#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 ?

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.

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?

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.

Do you have any suggestions or better solutions?

回答1:

The QObject implementation of the Composite Design Pattern has been tried and tested through the many versions of 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.

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().

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.

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: QPointer and QSharedPointer. Each has their pros and cons.

#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
}


回答2:

Adding to RobbiE's answer, the QPointer and QSharedPointer are two complemetary classes that serve different functions.

QPointer and its Caveats

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:

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.

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<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<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

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.

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.

The QWeakPointer is QSharedPointer's cousin. It is non-owning. It tracks whether the objects held by QSharedPointers 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.

A QPointer is like a QWeakPointer for QObjects, but it doesn't require the existence of 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.

QScopedPointer

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.

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 is a 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.

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.