Wie funktioniert delete und deleteLater in Bezug auf Signale und Slots in Qt?

78

Es gibt ein Objekt der Klasse QNetworkReply. Es gibt einen Steckplatz (in einem anderen Objekt), der mit seinem fertigen () Signal verbunden ist. Die Signale sind synchron (die Standardsignale). Es gibt nur einen Thread.

Irgendwann möchte ich beide Objekte loswerden. Keine Signale mehr oder irgendetwas von ihnen. Ich will, dass sie weg sind. Nun, ich dachte, ich werde verwenden

delete obj1; delete obj2;

Aber kann ich das wirklich? Die Spezifikationen für ~ QObject sagen:

Das Löschen eines QObjects, während ausstehende Ereignisse auf die Zustellung warten, kann zu einem Absturz führen.

Was sind die "ausstehenden Ereignisse"? Könnte das bedeuten, dass während ich meine anrufe delete, bereits einige "ausstehende Ereignisse" zugestellt werden müssen und dass sie einen Absturz verursachen können und ich nicht wirklich überprüfen kann, ob es welche gibt?

Nehmen wir also an, ich rufe an:

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

Sicher sein.

Aber bin ich wirklich sicher? Das deleteLaterfügt ein Ereignis hinzu, das in der Hauptschleife behandelt wird, wenn die Steuerung dort ankommt. Kann es einige ausstehende Ereignisse (Signale) für obj1oder obj2bereits geben, die darauf warten, in der Hauptschleife behandelt zu werden, bevor deleteLater behandelt wird? Das wäre sehr unglücklich. Ich möchte keinen Code schreiben, der den Status "etwas gelöscht" überprüft und das eingehende Signal in allen meinen Steckplätzen ignoriert.

stach
quelle
4
Sieht so aus, obj->disconnect(); obj->deleteLater();als wäre der richtige Weg:
Stach
1
Nach dem Lesen der QObject-Quelle scheint es, dass deleteLater()einfach ein a QDeferredDeleteEventan das Objekt gesendet wird, für das deleteLater()aufgerufen wurde. Wenn dieses Ereignis vom QObject empfangen wird, ruft sein Ereignishandler letztendlich regulär auf, deletewas wiederum den Destruktor des QObject aufruft. Die Signalunterbrechung erfolgt erst am Ende des Destruktors. Daher würde ich vermuten, dass das QObject Slots ausführt, die von DirectConnection-Signalen aufgerufen werden, die nach dem Aufruf von, deleteLater()aber bevor die Ereignisschleife zurückkehrt , ausgegeben werden .
Kasheen

Antworten:

74

Das Löschen von QObjects ist normalerweise sicher (dh in der normalen Praxis; es kann pathologische Fälle geben, die mir atm nicht bekannt sind), wenn Sie zwei Grundregeln befolgen:

  • Löschen Sie niemals ein Objekt in einem Slot oder einer Methode, die direkt oder indirekt von einem (synchronen, Verbindungstyp "direkt") Signal aus dem zu löschenden Objekt aufgerufen wird. Wenn Sie beispielsweise eine Klasse Operation mit einem Signal Operation :: finish () und einem Slot Manager :: operationFinished () haben, möchten Sie das Operationsobjekt, das das Signal in diesem Slot ausgegeben hat, nicht löschen. Die Methode, die das Signal finish () ausgibt, greift möglicherweise nach der Ausgabe weiterhin auf "this" zu (z. B. auf ein Mitglied) und bearbeitet dann einen ungültigen "this" -Zeiger.

  • Löschen Sie niemals ein Objekt in Code, der synchron aus der Ereignisbehandlungsroutine des Objekts aufgerufen wird. Löschen Sie beispielsweise kein SomeWidget in SomeWidget :: fooEvent () oder in Methoden / Slots, die Sie von dort aus aufrufen. Das Ereignissystem arbeitet weiterhin mit dem bereits gelöschten Objekt -> Absturz.

Beides kann schwierig zu finden sein, da die Rückverfolgungen normalerweise seltsam aussehen (wie Absturz beim Zugriff auf eine POD-Mitgliedsvariable), insbesondere wenn Sie komplizierte Signal- / Slot-Ketten haben, bei denen ein Löschen mehrere Schritte nach unten erfolgen kann, das ursprünglich durch ein Signal oder ein Ereignis von ausgelöst wurde das Objekt, das gelöscht wird.

Solche Fälle sind der häufigste Anwendungsfall für deleteLater (). Es stellt sicher, dass das aktuelle Ereignis abgeschlossen werden kann, bevor das Steuerelement zur Ereignisschleife zurückkehrt, die dann das Objekt löscht. Eine andere, ich finde oft bessere Möglichkeit, die gesamte Aktion zu verschieben, indem eine Verbindung in der Warteschlange / QMetaObject :: invokeMethod (..., Qt :: QueuedConnection) verwendet wird.

Frank Osterfeld
quelle
1
Ein Beispiel für diesen Absturz wäre: Aus dem focusOut-Ereignis eines Widgets lösche ich einige untergeordnete Widgets. Der Fokus wird durch einen Klick in eines der zu löschenden Widgets ausgelöst. In diesem Beispiel ist das Löschen nicht sicher, da das Objekt bei Erreichen der Ereignisschleife bereits verschwunden ist und einen Absturz verursacht, wenn versucht wird, ein Klickereignis an dieses Widget zu senden. deleteLater ist sicher, da das Objekt zum Löschen markiert ist und die Ereignisschleife weiß, dass dieses Ereignis nicht
zugestellt werden
22

Die nächsten beiden Zeilen Ihrer verwiesenen Dokumente enthalten die Antwort.

Von ~ QObject ,

Das Löschen eines QObjects, während ausstehende Ereignisse auf die Zustellung warten, kann zu einem Absturz führen. Sie dürfen das QObject nicht direkt löschen, wenn es in einem anderen Thread als dem aktuell ausgeführten vorhanden ist. Verwenden Sie stattdessen deleteLater (), wodurch die Ereignisschleife das Objekt löscht, nachdem alle ausstehenden Ereignisse an das Objekt gesendet wurden.

Es heißt ausdrücklich, dass wir nicht aus anderen Threads löschen sollen. Da Sie eine einzelne Thread-Anwendung haben, ist das Löschen sicher QObject.

Andernfalls, wenn Sie es in einer Multithread-Umgebung deleteLater()löschen müssen , verwenden Sie diese Option, um Ihre zu löschen, QObjectsobald die Verarbeitung aller Ereignisse abgeschlossen ist.

liaK
quelle
6
Was ist mit meinem zweiten Szenario? Können Slots in einem Objekt noch aufgerufen werden, nachdem ich deleteLater darauf aufgerufen habe?
Stach
15

Sie finden eine Antwort auf Ihre Frage zu einer der Delta-Objektregeln, in der Folgendes angegeben ist:

Signal Safe (SS).
Es muss sicher sein, Methoden für das Objekt, einschließlich des Destruktors, aus einem Slot heraus aufzurufen, der von einem seiner Signale aufgerufen wird.

Fragment:

Im Kern unterstützt QObject das Löschen während der Signalisierung. Um dies nutzen zu können, müssen Sie nur sicherstellen, dass Ihr Objekt nach dem Löschen nicht versucht, auf eines seiner eigenen Mitglieder zuzugreifen. Die meisten Qt-Objekte sind jedoch nicht so geschrieben, und es ist auch nicht erforderlich, dass sie es sind. Aus diesem Grund wird empfohlen, immer deleteLater () aufzurufen, wenn Sie ein Objekt während eines seiner Signale löschen müssen, da die Wahrscheinlichkeit besteht, dass 'delete' die Anwendung nur zum Absturz bringt.

Leider ist nicht immer klar, wann Sie 'delete' vs deleteLater () verwenden sollten. Das heißt, es ist nicht immer offensichtlich, dass ein Codepfad eine Signalquelle hat. Oft haben Sie einen Codeblock, der für einige heute sichere Objekte "Löschen" verwendet. Irgendwann in der Zukunft wird dieser Codeblock jedoch von einer Signalquelle aufgerufen, und jetzt stürzt Ihre Anwendung plötzlich ab. Die einzige allgemeine Lösung für dieses Problem besteht darin, ständig deleteLater () zu verwenden, auch wenn dies auf einen Blick unnötig erscheint.

Im Allgemeinen betrachte ich Delta Object Rules als obligatorische Lektüre für jeden Qt-Entwickler. Es ist ausgezeichnetes Lesematerial.

Piotr Dobrogost
quelle
1
Wenn Sie dem Link zu DOR folgen, müssen Sie den Links auf dieser Seite zum weiteren Lesen folgen, z. B. dem Link zu "Signal Safe". Die erste Seite des Links ist ohne Kontext schwer zu verstehen. (Ich jage einen Absturz beim Beenden mit PyQt unter Windows, meine App löscht nicht einmal Objekte, aber ich hoffe, der Link zu DOR bietet Einblicke.)
Bootchk
4

Soweit ich weiß, ist dies hauptsächlich ein Problem, wenn die Objekte in verschiedenen Threads vorhanden sind. Oder vielleicht während Sie die Signale tatsächlich verarbeiten.

Andernfalls werden beim Löschen eines QObject zunächst alle Signale und Slots getrennt und alle ausstehenden Ereignisse entfernt. Da würde ein Aufruf zum Trennen () genügen.

thorsten müller
quelle