How delete and deleteLater works with regards to signals and slots in Qt?
Asked Answered
C

5

83

There is an object of class QNetworkReply. There is a slot (in some other object) connected to its finished() signal. Signals are synchronous (the default ones). There is only one thread.

At some moment of time I want to get rid of both of the objects. No more signals or anything from them. I want them gone. Well, I thought, I'll use

delete obj1; delete obj2;

But can I really? The specs for ~QObject say:

Deleting a QObject while pending events are waiting to be delivered can cause a crash.

What are the 'pending events'? Could that mean that while I'm calling my delete, there are already some 'pending events' to be delivered and that they may cause a crash and I cannot really check if there are any?

So let's say I call:

obj1->deleteLater(); obj2->deleteLater();

To be safe.

But, am I really safe? The deleteLater adds an event that will be handled in the main loop when control gets there. Can there be some pending events (signals) for obj1 or obj2 already there, waiting to be handled in the main loop before deleteLater will be handled? That would be very unfortunate. I don't want to write code checking for 'somewhat deleted' status and ignoring the incoming signal in all of my slots.

Capacitate answered 3/2, 2011 at 15:39 Comment(2)
Looks like obj->disconnect(); obj->deleteLater(); is the right way to go:Capacitate
Having read the QObject source it seems that deleteLater() simply posts a QDeferredDeleteEvent to the object that deleteLater() was invoked on. When that event is received by the QObject its event handler will ultimately invoke regular delete which in turn calls the QObject's destructor. Signal disconnection does not occur until the end of the destructor, therefore I'd guess that the QObject will run slots which are invoked by DirectConnection signals which are emitted after the call to deleteLater() but before the event loop returns.Weber
M
75

Deleting QObjects is usually safe (i.e. in normal practice; there might be pathological cases I am not aware of atm), if you follow two basic rules:

  • Never delete an object in a slot or method that is called directly or indirectly by a (synchronous, connection type "direct") signal from the object to be deleted. E.g. if you have a class Operation with a signal Operation::finished() and a slot Manager::operationFinished(), you don't want delete the operation object that emitted the signal in that slot. The method emitting the finished() signal might continue accessing "this" after the emit (e.g. accessing a member), and then operate on an invalid "this" pointer.

  • Likewise, never delete an object in code that is called synchronously from the object's event handler. E.g. don't delete a SomeWidget in its SomeWidget::fooEvent() or in methods/slots you call from there. The event system will continue operating on the already deleted object -> Crash.

Both can be tricky to track down, as the backtraces usually look strange (Like crash while accessing a POD member variable), especially when you have complicated signal/slot chains where a deletion might occur several steps down originally initiated by a signal or event from the object that is deleted.

Such cases are the most common use case for deleteLater(). It makes sure that the current event can be completed before the control returns to the event loop, which then deletes the object. Another, I find often better way is defer the whole action by using a queued connection/QMetaObject::invokeMethod( ..., Qt::QueuedConnection ).

Mcripley answered 3/2, 2011 at 17:22 Comment(1)
One example of this crash would be: from some widget's focusOut event I delete some children widgets. The focus out is triggered by a click in one of the widgets to be deleted. In this example delete is not safe because when the event loop is reached the object is already gone and causes a crash when it tries to deliver a click event to that widget. deleteLater is safe because the object is marked for deletion and the event loop knows that this event should not be delivered because the object has been already deletedHeinrick
I
23

The next two lines of your referred docs says the answer.

From ~QObject,

Deleting a QObject while pending events are waiting to be delivered can cause a crash. You must not delete the QObject directly if it exists in a different thread than the one currently executing. Use deleteLater() instead, which will cause the event loop to delete the object after all pending events have been delivered to it.

It specifically says us to not to delete from other threads. Since you have a single threaded application, it is safe to delete QObject.

Else, if you have to delete it in a multi-threaded environment, use deleteLater() which will delete your QObject once the processing of all the events have been done.

Infix answered 4/2, 2011 at 5:17 Comment(2)
What about my second scenario? Can slots in an object still be called after I call deleteLater on it?Capacitate
@stach, seems that this deserves a separate question. And better on the QtForum.Tereus
T
14

You can find answer to your question reading about one of the Delta Object Rules which states this:

Signal Safe (SS).
It must be safe to call methods on the object, including the destructor, from within a slot being called by one of its signals.

Fragment:

At its core, QObject supports being deleted while signaling. In order to take advantage of it you just have to be sure your object does not try to access any of its own members after being deleted. However, most Qt objects are not written this way, and there is no requirement for them to be either. For this reason, it is recommended that you always call deleteLater() if you need to delete an object during one of its signals, because odds are that ‘delete’ will just crash the application.

Unfortunately, it is not always clear when you should use ‘delete’ vs deleteLater(). That is, it is not always obvious that a code path has a signal source. Often, you might have a block of code that uses ‘delete’ on some objects that is safe today, but at some point in the future this same block of code ends up getting invoked from a signal source and now suddenly your application is crashing. The only general solution to this problem is to use deleteLater() all the time, even if at a glance it seems unnecessary.

Generally I regard Delta Object Rules as obligatory read for every Qt developer. It's excellent reading material.

Tahsildar answered 3/2, 2011 at 20:55 Comment(3)
If you follow the link to DOR, you must follow the links on that page for further reading, e.g. follow the link to 'Signal Safe.' The first page from the link is hard to understand without context. (I'm chasing a crash on exit using PyQt on Windows, my app is not even deleting any objects, but I hope the link to DOR will offer insights.)Neurasthenia
The link to DOR is broken. Is there a new/replacement link for this?Sherrer
@Sherrer You can access historic versions at web.archive.orgTahsildar
D
4

As far as I know, this is mainly an issue if the objects exist in different threads. Or maybe while you are actually processing the signals.

Otherwise deleting a QObject will first disconnect all signals and slots and remove all pending events. As a call to disconnect() would do.

Diffractive answered 3/2, 2011 at 17:27 Comment(0)
I
0

Even deleteLater may not be sufficient.

Imagine that your main thread event loop is currently servicing some event/slot using object X, but it goes back to the event loop (This can happen when emitting signal/wait/sleep with Qt dirty interface). The main thread continues to service another event that delete or request the deleteLater on the parent of X. If it is deleteLater, it is still possible that the main thread will return to the event loop then delete the parent of X.

When parent of X is deleted, it would delete X. This would cause the main thread to return back to the earlier event/slot that it was servicing part way, and crash when trying to use the deleted object X.

Just pointing out the deleteLater is not sufficient.

Imalda answered 21/12, 2023 at 2:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.