83 Stimmen

Wie funktionieren delete und deleteLater im Zusammenhang mit Signalen und Slots in Qt?

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

Irgendwann möchte ich die beiden Objekte loswerden. Keine Signale oder irgendetwas mehr von ihnen. Ich will sie loswerden. Nun, ich dachte, ich benutze

delete obj1; delete obj2;

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

Das Löschen eines QObjects, während anstehende Ereignisse darauf warten, zugestellt zu werden, kann einen Absturz verursachen.

Was sind die "anstehenden Ereignisse"? Könnte das bedeuten, dass ich beim Aufrufen meiner delete dass es bereits einige "ausstehende Ereignisse" gibt, die zugestellt werden müssen, und dass diese einen Absturz verursachen können, und ich kann nicht wirklich überprüfen, ob es welche gibt?

Sagen wir also, ich rufe an:

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

Um sicher zu gehen.

Aber bin ich wirklich sicher? Die deleteLater fügt ein Ereignis hinzu, das in der Hauptschleife behandelt wird, wenn die Steuerung dort ankommt. Kann es einige anstehende Ereignisse (Signale) für obj1 o obj2 bereits vorhanden und warten darauf, in der Hauptschleife bearbeitet zu werden vor deleteLater gehandhabt wird? Das wäre sehr unglücklich. Ich möchte keinen Code schreiben, der auf den Status "etwas gelöscht" prüft und das eingehende Signal in allen meinen Slots ignoriert.

5 Stimmen

Sieht aus wie obj->disconnect(); obj->deleteLater(); ist der richtige Weg:

1 Stimmen

Nachdem ich den QObject-Quelltext gelesen habe, scheint es, dass deleteLater() stellt einfach eine QDeferredDeleteEvent zu dem Objekt, das deleteLater() wurde aufgerufen am. Wenn dieses Ereignis vom QObject empfangen wird, ruft sein Event-Handler schließlich reguläre delete der wiederum den Destruktor des QObjects aufruft. Die Signaltrennung erfolgt erst am Ende des Destruktors, daher würde ich vermuten, dass das QObject Slots ausführen wird, die durch DirectConnection-Signale aufgerufen werden, die nach dem Aufruf von deleteLater() aber bevor die Ereignisschleife zurückkehrt.

75voto

Frank Osterfeld Punkte 24349

Das Löschen von QObjects ist in der Regel sicher (d.h. in der normalen Praxis; es könnte pathologische Fälle geben, von denen ich derzeit nichts weiß), wenn Sie zwei Grundregeln beachten:

  • Löschen Sie niemals ein Objekt in einem Slot oder einer Methode, die direkt oder indirekt durch ein (synchrones, verbindungsartiges "direktes") Signal von dem zu löschenden Objekt aufgerufen wird. Wenn Sie z.B. eine Klasse Operation mit einem Signal Operation::finished() und einem Slot Manager::operationFinished() haben, wollen Sie nicht das Objekt Operation löschen, das das Signal in diesem Slot gesendet hat. Die Methode, die das Signal finished() sendet, könnte nach dem Emit weiterhin auf "this" zugreifen (z.B. auf ein Member) und dann auf einen ungültigen "this"-Zeiger arbeiten.

  • Löschen Sie auch niemals ein Objekt in einem Code, der synchron vom Ereignishandler des Objekts aufgerufen wird. Löschen Sie z.B. ein SomeWidget nicht in seinem SomeWidget::fooEvent() oder in Methoden/Slots, die Sie von dort aus aufrufen. Das Ereignissystem wird mit dem bereits gelöschten Objekt weiterarbeiten -> Absturz.

Beides kann schwierig aufzuspüren sein, da die Backtraces in der Regel seltsam aussehen (z. B. Absturz beim Zugriff auf eine POD-Mitgliedsvariable), vor allem, wenn Sie komplizierte Signal-/Slot-Ketten haben, bei denen eine Löschung mehrere Schritte nach unten erfolgen kann, die ursprünglich durch ein Signal oder Ereignis des gelöschten Objekts ausgelöst wurden.

Solche Fälle sind der häufigste Anwendungsfall für deleteLater(). Sie stellt sicher, dass das aktuelle Ereignis abgeschlossen werden kann, bevor die Steuerung zur Ereignisschleife zurückkehrt, die dann das Objekt löscht. Ein anderer, meiner Meinung nach oft besserer Weg ist die Verschiebung der gesamten Aktion durch Verwendung einer Warteschlangenverbindung/QMetaObject::invokeMethod( ..., Qt::QueuedConnection ).

23voto

liaK Punkte 11212

Die nächsten beiden Zeilen der von Ihnen genannten Dokumente enthalten die Antwort.

Von ~QObjekt ,

Das Löschen eines QObjects, während anstehende Ereignisse darauf warten, zugestellt zu werden, kann einen Absturz verursachen. Sie darf das QObject nicht direkt löschen, wenn es in einem anderen Thread existiert als dem, der gerade ausgeführt wird. Verwenden Sie stattdessen die Funktion deleteLater(), die die Ereignisschleife veranlasst, das Objekt zu löschen, nachdem alle anstehenden Ereignisse an es übermittelt wurden.

Es wird ausdrücklich gesagt, dass wir nicht aus anderen Threads löschen sollen. Da Sie eine Anwendung mit nur einem Thread haben, ist es sicher, zu löschen QObject .

Andernfalls, wenn Sie es in einer Multi-Thread-Umgebung löschen müssen, verwenden Sie deleteLater() die Ihre Daten löschen wird QObject sobald die Verarbeitung aller Ereignisse abgeschlossen ist.

14voto

Piotr Dobrogost Punkte 39650

Sie können die Antwort auf Ihre Frage finden, wenn Sie über eine der Delta-Objekt-Regeln die Folgendes besagt:

Signal Safe (SS).
Es muss sicher sein, dass Methoden des Objekts aufzurufen, einschließlich den Destruktor, aus einem Slot heraus aufrufen der durch eines seiner Signale aufgerufen wird.

Fragment:

In seinem Kern unterstützt QObject während der Signalisierung gelöscht werden. Um die Vorteil daraus zu ziehen, müssen Sie nur sicher sein, dass Ihr Objekt nicht versucht auf eines seiner eigenen Mitglieder zuzugreifen, nachdem gelöscht wurde. Allerdings sind die meisten Qt Objekte sind jedoch nicht auf diese Weise geschrieben, und es ist auch nicht erforderlich, dass sie es sind auch nicht. Aus diesem Grund ist es empfohlen, dass Sie immer deleteLater() aufzurufen, wenn Sie ein Objekt während eines seiner Signale löschen müssen, denn die Chancen stehen gut, dass 'delete' die Anwendung einfach zum Absturz bringt.

Leider ist es nicht immer klar wann man 'delete' gegenüber deleteLater() verwenden sollte. Das heißt, es ist nicht nicht immer offensichtlich, dass ein Code-Pfad eine Signalquelle hat. Oft haben Sie vielleicht einen Code-Block, der 'delete' auf einigen Objekten verwendet, der heute sicher ist, aber irgendwann in der Zukunft wird derselbe Code-Block von einer Signalquelle aufgerufen von einer Signalquelle aufgerufen wird und nun plötzlich stürzt Ihre Anwendung ab. Die einzige allgemeine Lösung für dieses Problem ist die deleteLater() immer zu verwenden, auch wenn auch wenn es auf den ersten Blick unnötig erscheint.

Im Allgemeinen betrachte ich Delta-Objekt-Regeln als obligatorische Lektüre für jeden Qt-Entwickler. Es ist ausgezeichneter Lesestoff.

4voto

thorsten müller Punkte 5581

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

Andernfalls werden beim Löschen eines QObjects zunächst alle Signale und Slots abgeschaltet und alle anstehenden Ereignisse entfernt. Wie es ein Aufruf von disconnect() tun würde.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X