Warum verwenden die offiziellen Qt-Beispiele und -Tutorials keine intelligenten Zeiger?

76

Warum verwenden offizielle Beispiele und Tutorials zur Qt-Bibliothek niemals intelligente Zeiger? Ich sehe nur newund deletezum Erstellen und Zerstören der Widgets.

Ich habe nach dem Grund gesucht, konnte ihn aber nicht finden, und ich sehe selbst keinen, außer aus historischen Gründen oder aus Gründen der Abwärtskompatibilität: Nicht jeder möchte, dass das Programm beendet wird, wenn ein Widget-Konstruktor ausfällt, und es über try / catch verarbeitet Blöcke sind einfach hässlich (auch wenn sie an wenigen Stellen verwendet werden). Die Tatsache, dass die Eltern-Widgets möglicherweise das Eigentum der Kinder übernehmen, erklärt mir die Sache auch nur teilweise, da Sie sie deleteauf einer bestimmten Ebene immer noch für die Eltern verwenden müssten .

Martin
quelle
9
Qt hatte viel vor dem modernen C ++ intelligente Zeiger. Und diese werden in einigen Projekten verwendet. Sie sehen dies in diesen Beispielen nicht, da QObject alle Probleme im Zusammenhang mit Kindern und Eltern und den Lebenszyklus behandelt. Weitere Informationen zu Qt Smart Pointern finden Sie unter -> wiki.qt.io/Smart_Pointers
Sanjay
2
"Sie müssten auf einer bestimmten Ebene immer noch" Löschen "für die Eltern verwenden" - nicht unbedingt, da das Stammobjekt normalerweise darin sitzt main()und automatisch erfasst wird.
dtech
Es scheint, dass QPointer in verschiedenen Kontexten transparent verwendet werden kann: dh es verhält sich wie ein "Zeiger mit Gültigkeitsbereich", wenn das referenzierte Widget NICHT zu einem übergeordneten Objekt hinzugefügt wurde, bevor der QPointer den Gültigkeitsbereich verlässt, das Widget jedoch erhalten bleibt Leben, wenn es einem Elternteil hinzugefügt wurde. Bevor der QPointer außer Reichweite gerät
Martin
Es ist nicht so, dass einfache Zeiger veraltet waren oder so, sie sind so nützlich wie nie zuvor. Im Gegensatz zu dem, was manche Leute sagen, ist es immer noch vollkommen in Ordnung, sie zu verwenden, und im Fall von Qt gibt es viele Sicherheitsvorkehrungen um die "scharfen Ecken" von einfachen Zeigern. Aber ja, lassen Sie uns jetzt aufhören, Zeiger zu verwenden, nur weil C ++ endlich beschlossen hat, intelligente Zeiger zu integrieren.
dtech
5
@ddriver Es ist erwähnenswert, dass der Expertenrat nicht "niemals rohe Zeiger verwenden" lautet. Der Rat lautet "Vermeiden Sie den Besitz von Rohzeigern". Das ist eine wichtige Unterscheidung, die Sie anscheinend nicht treffen.
Tim Seguine

Antworten:

67

Weil Qt ein Eltern-Kind-Modell verwendet, um Qobject-Ressourcen zu verwalten. Es folgt dem Composite + Chain-of-Responsibility-Muster, das von der Ereignisverwaltung über die Speicherverwaltung, das Zeichnen, die Dateiverwaltung usw. verwendet wird.

Der Versuch, ein QObject in einem gemeinsam genutzten \ eindeutigen Zeiger zu verwenden, ist in 99% der Fälle überentwickelt .

  1. Sie müssen einen benutzerdefinierten Löscher angeben, der deleteLater aufruft
  2. Ihr qObject mit Eltern hat bereits eine Referenz im übergeordneten Objekt. Sie wissen also, dass ein Objekt nicht durchgesickert ist, solange das übergeordnete Objekt vorhanden ist. Wenn Sie es loswerden müssen, können Sie deleteLaterdirekt anrufen .
  3. Ihr QWidget ohne übergeordnetes Element verfügt bereits über eine Referenz im Qapplication-Objekt . So wie Punkt 2.

Trotzdem können Sie RAII weiterhin mit Qt verwenden. Zum Beispiel verhält sich QPointer als schwache Referenz auf a QObject. Ich würde QPointer<QWidget>eher verwenden als QWidget*.

Hinweis: Um nicht zu fanboy zu klingen, zwei Wörter: Qt + Valgrind.

UmNyobe
quelle
2
Punkt 4. Wenn das übergeordnete Element gelöscht wird, haben shared_ptr und unique_ptr einen baumelnden Zeiger. Dies wird durch den QPointer gelöst, der von dem QObject, auf das verwiesen wird, automatisch gelöscht wird.
Ratschenfreak
Ist es für Nicht-Widget-Objekte ohne Eltern weiterhin erforderlich, das Objekt aufzurufen, deleteLateranstatt es einfach zu deletebearbeiten? Ich verwalte häufig Instanzen von QObject-abgeleiteten Klassen mithilfe intelligenter Zeiger. Daher habe ich zuvor nach einer Antwort darauf gesucht, um sicherzustellen, dass ich nichts Gefährliches tue, und soweit ich das beurteilen kann, gibt die Dokumentation nicht an, dass dies der Fall ist falsch zu deleteeinem QObject Zeiger normal (auch wenn es sich von einem übergeordneten Objekt gehört, weil der Destruktor der Eltern Referenz wird entfernen).
Kyle Strand
2
Dokumentation warnt Sie davor . Der direkte Aufruf von delete kann zu Abstürzen aufgrund ausstehender Ereignisse \ Verbindungen in der Warteschlange zum Objekt (oder seinen untergeordneten Objekten) führen.
UmNyobe
@UmNyobe Das stimmt, wenn Sie eine QObjectvon einer anderen löschen QThread. Der Destruktor von QObjecttrennt alle Signale von und zu ihm und entfernt ausstehende Ereignisse automatisch aus seiner Ereigniswarteschlange. Daher ist das Aufrufen deletevon a QObjectin Ordnung, wenn Sie dies innerhalb des Threads tun. Davon deleteLater()abgesehen können Sie kaum etwas falsch machen, außer wenn Sie aus irgendeinem Grund eine sofortige Reinigung benötigen.
Ralph Tandetzky
@UmNyobe Laut der QPointer-Dokumentation hat dies nichts mit RAII zu tun. Es wird lediglich garantiert, dass der Zeiger 0 ist, wenn jemand anderes das QObject, auf das er zeigt, deltet. Das QObject selbst wird jedoch nie gelöscht. Wenn Sie RAII verwenden, sollte QSharedPointer die bevorzugte Methode sein.
Fritz
26

Intelligente Zeiger auf Kinder

Die Smart Pointer Klassen std::unique_ptrund std::shared_ptrdienen der Speicherverwaltung. Mit einem solchen intelligenten Zeiger bedeutet, dass Sie besitzen den Zeiger. Wenn Sie jedoch einen QObjectoder einen abgeleiteten Typ mit einem QObjectübergeordneten Element erstellen , wird das Eigentum (die Verantwortung für die Bereinigung) an das übergeordnete Element übergeben QObject. In diesem Fall sind die intelligenten Zeiger der Standardbibliothek unnötig oder sogar gefährlich, da sie möglicherweise zu einer doppelten Löschung führen können. Huch!

Rohe Zeiger auf Waisenkinder

Wenn jedoch ein QObject(oder ein abgeleiteter Typ) auf dem Heap ohne übergeordnetes QObjectElement erstellt wird, sind die Dinge sehr unterschiedlich. In diesem Fall sollten Sie nicht nur einen Rohzeiger halten, sondern einen intelligenten Zeiger, vorzugsweise einen std::unique_ptrauf das Objekt. Auf diese Weise erhalten Sie Ressourcensicherheit. Wenn Sie das Objekt später einem übergeordneten Objekt übergeben, QObjectkönnen Sie Folgendes verwenden std::unique_ptr<T>::release():

auto obj = std::make_unique<MyObject>();
// ... do some stuff that might throw ...
QObject parentObject;
obj->setParent( &parentObject );
obj.release();

Wenn das, was Sie tun, bevor Sie Ihrem Waisenkind ein Elternteil geben, eine Ausnahme auslöst, liegt ein Speicherverlust vor, wenn Sie den Rohzeiger zum Halten des Objekts verwenden. Der obige Code ist jedoch gegen ein solches Leck geschützt.

Allgemeiner gesagt

Es ist kein moderner C ++ - Rat, Rohzeiger insgesamt zu vermeiden, sondern zu vermeiden, Rohzeiger zu besitzen . Ich könnte einen weiteren modernen C ++ - Rat hinzufügen: Verwenden Sie keine intelligenten Zeiger für Objekte, die einer anderen Programmentität gehören.

Ralph Tandetzky
quelle
4
Ja. Dies. Eigentum ist alles. Ich wünschte, die Qt-Dokumentation wäre etwas expliziter. Es scheint viele Hinweise darauf zu geben, dass Kinder "automatisch" gelöscht werden, aber nicht viele Informationen darüber, wie mit Waisen umgegangen werden soll.
Kyle Strand
+1, insbesondere für den letzten Absatz. Ich habe unzählige Gelegenheiten gesehen, in denen Menschen unermüdlich versuchen, intelligente Zeiger zu verwenden, wo immer sie können (insbesondere zur Parameterübergabe siehe auch diese
Kernrichtlinie
13

Sie haben bereits Ihre eigene Frage beantwortet : except if it's for historic reasons/backward compatibility. Eine Bibliothek, die so groß wie QT ist, kann nicht davon ausgehen, dass jeder, der die Bibliothek verwendet, über Compiler verfügt, die C ++ 11 unterstützen. newund deletesind in früheren Standards garantiert vorhanden.

Wenn Sie jedoch die Unterstützung für die Verwendung intelligenter Zeiger haben, würde ich empfehlen, diese über Rohzeigern zu verwenden.

Hatted Rooster
quelle
Es gab jedoch intelligente Zeiger vor C ++ 11 und wahrscheinlich vor QT. War es von Anfang an eine schlechte Designentscheidung?
Martin
21
äh ... als qt und einige andere Bibliotheken entworfen und implementiert wurden, hatte c ++ nicht einmal eine Standard-String-Klasse.
UmNyobe
Wenn intelligente Zeiger vor C ++ 11 bedeuten auto_ptr, war es die richtige Entscheidung, sie nicht zu verwenden. Vor C ++ 11 gab es keine guten standardisierten Smart Pointer.
Zyx 2000
2
Keine guten standardisierten, aber einige Qt-basierte, z . B. QSharedPointerund QScopedPointer.
Ruslan
Diese Antwort ist veraltet, da modernes Qt C ++ 11-Unterstützung erfordert.
Mikhail
10

Zusätzlich zu dem, was @Jamey gesagt hat:

Wenn Sie es geschickt gestalten, müssen Sie möglicherweise nie ein Löschen für ein Widget verwenden. Nehmen wir an, Sie haben ein Hauptfenster und erstellen ein automatisches Objekt davon und führen dieses Fenster in einer Ereignisschleife aus. Jetzt können alle Elemente in diesem Widget als untergeordnete Elemente hinzugefügt werden. Und da Sie sie als Kind direkt / indirekt zu diesem MainWindow hinzufügen, wird beim Schließen dieses Hauptfensters automatisch alles erledigt. Sie müssen lediglich sicherstellen, dass alle von Ihnen erstellten dynamischen Objekte / Widgets Kinder / Enkel des MainWindow sind. Daher ist kein explizites Löschen erforderlich.

PRIME
quelle
2
Sie müssen jedes Kind zum Hauptfenster hinzufügen, sobald Sie es erstellen oder zumindest bevor etwas, das geworfen werden kann oder das Ding nicht funktioniert, eine Einschränkung, an die ich mich gerne erinnern möchte
Martin
2
@ Martin wirft normalerweise nichts in Qt-Apps (es sei denn, man ist ein echter Fan), also komm schon :)
mlvljr
5
  • QObject hat ein übergeordnetes Element definiert und die baumartige Struktur des Programms ermöglicht eine recht effektive Speicherverwaltung.

  • Die Dynamik in Qt bricht dieses schöne Ideal, z. B. das Weitergeben eines rohen Zeigers. Man kann leicht ein halten dangling pointer, aber das ist ein häufiges Problem bei der Programmierung.

  • Der Qt Smart Pointer, eigentlich eine schwache Referenz, ist QPointer<T>
    und liefert einige der STL-Süßigkeiten.

  • Man kann auch mit std::unique_ptrund dergleichen mischen , aber es sollte nur für Nicht-Qt-Maschinen in Ihrem Programm verwendet werden.

g24l
quelle