it-swarm.com.de

Zeiger, intelligente Zeiger oder gemeinsame Zeiger?

Ich programmiere mit normalen Zeigern, aber ich habe von Bibliotheken wie Boost gehört, die intelligente Zeiger implementieren. Ich habe auch gesehen, dass es in der Ogre3D-Rendering-Engine eine tiefe Verwendung von geteilten Zeigern gibt.

Was genau ist der Unterschied zwischen den drei und sollte ich nur eine Art von ihnen verwenden?

113
tunnuz

Sydius hat die Typen ziemlich gut umrissen:

  • Normale Zeiger sind genau das - sie zeigen irgendwo auf etwas im Gedächtnis. Wem gehört es? Nur die Kommentare werden Sie wissen lassen. Wer befreit es? Hoffentlich der Besitzer irgendwann.
  • Intelligente Zeiger sind ein Überbegriff, der viele Typen abdeckt. Ich gehe davon aus, dass Sie einen Bereichszeiger gemeint haben, der das Muster RAII verwendet. Es ist ein stapelzugeordnetes Objekt, das einen Zeiger umschließt. Wenn der Gültigkeitsbereich verlassen wird, wird delete für den umgebrochenen Zeiger aufgerufen. Es "besitzt" den enthaltenen Zeiger, indem es dafür verantwortlich ist, ihn irgendwann zu löschen. Sie ermöglichen es Ihnen, einen Rohverweis auf den Zeiger abzurufen, den sie für die Übergabe an andere Methoden umschließen, sowie den Zeiger zu freigeben, sodass andere ihn besitzen können. Das Kopieren macht keinen Sinn.
  • Shared Pointer ist ein von einem Stapel zugewiesenes Objekt, das einen Zeiger umschließt, damit Sie nicht wissen müssen, wem er gehört. Wenn der letzte gemeinsame Zeiger für ein Objekt im Speicher zerstört wird, wird auch der umgebrochene Zeiger gelöscht.

Wie wäre es, wenn Sie sie verwenden sollten? Sie werden entweder umfangreiche Zeiger oder gemeinsame Zeiger verwenden. Wie viele Threads werden in Ihrer Anwendung ausgeführt? Wenn die Antwort "potenziell viel" lautet, können sich gemeinsame Zeiger als Leistungsengpass herausstellen, wenn sie überall verwendet werden. Der Grund dafür ist, dass das Erstellen/Kopieren/Zerstören eines freigegebenen Zeigers eine atomare Operation sein muss. Dies kann die Leistung beeinträchtigen, wenn viele Threads ausgeführt werden. Dies wird jedoch nicht immer der Fall sein - dies können Sie nur beim Testen feststellen.

Es gibt ein Argument (das ich mag) gegen gemeinsame Zeiger - wenn Sie sie verwenden, können Programmierer ignorieren, wem ein Zeiger gehört. Dies kann zu kniffligen Situationen mit Zirkelverweisen führen (Java erkennt diese, gemeinsame Zeiger jedoch nicht) oder zu einer allgemeinen Programmierfaulheit in einer großen Codebasis.

Es gibt zwei Gründe, Bereichszeiger zu verwenden. Die erste dient der einfachen Ausnahmesicherheit und Bereinigung. Wenn Sie sicherstellen möchten, dass ein Objekt trotz Ausnahmen bereinigt wird, und Sie dieses Objekt nicht stapelweise zuordnen möchten, platzieren Sie es in einem Bereichszeiger. Wenn die Operation erfolgreich ist, können Sie sie jederzeit auf einen freigegebenen Zeiger übertragen. Speichern Sie den Overhead jedoch in der Zwischenzeit mit einem Zeiger mit Gültigkeitsbereich.

Der andere Fall ist, wenn Sie klare Objekteigentümer haben möchten. Einige Teams bevorzugen dies, andere nicht. Beispielsweise kann eine Datenstruktur Zeiger auf interne Objekte zurückgeben. Unter einem Bereichszeiger würde er einen Rohzeiger oder eine Referenz zurückgeben, die als schwache Referenz behandelt werden sollte. Es ist ein Fehler, auf diesen Zeiger zuzugreifen, nachdem die Datenstruktur, deren Eigentümer er ist, zerstört wurde, und es ist ein Fehler, sie zu löschen. Unter einem gemeinsam genutzten Zeiger kann das besitzende Objekt die zurückgegebenen internen Daten nicht zerstören, wenn noch ein Handle dafür vorhanden ist. Dies kann dazu führen, dass Ressourcen viel länger offen bleiben als erforderlich oder je nach Code noch viel schlimmer.

143
hazzen

der Begriff "intelligenter Zeiger" beinhaltet gemeinsame Zeiger, automatische Zeiger, Sperrzeiger und andere. Sie wollten Auto-Zeiger sagen (mehrdeutig als "Besitzer-Zeiger" bezeichnet), nicht Smart-Zeiger.

Dumme Zeiger (T *) sind niemals die beste Lösung. Sie führen eine explizite Speicherverwaltung durch, die ausführlich, fehleranfällig und manchmal nahezu unmöglich ist. Aber was noch wichtiger ist, sie signalisieren nicht Ihre Absicht.

Auto Pointer löschen die Spitze bei Zerstörung. Für Arrays bevorzugen Sie Kapseln wie vector und deque. Bei anderen Objekten ist es sehr selten erforderlich, sie auf dem Haufen zu speichern. Verwenden Sie einfach die lokalen Einstellungen und die Objektzusammensetzung. Bei Funktionen, die Heap-Zeiger zurückgeben, wie z. B. Fabriken und polymorphe Rückgaben, besteht weiterhin der Bedarf an automatischen Zeigern.

Gemeinsame Zeiger löschen die Spitze, wenn der letzte gemeinsame Zeiger darauf zerstört wird. Dies ist nützlich, wenn Sie ein einfaches, offenes Speicherschema wünschen, bei dem die erwartete Lebensdauer und der erwartete Besitz je nach Situation stark variieren können. Aufgrund der Notwendigkeit, einen (Atom-) Zähler zu führen, sind sie etwas langsamer als automatische Zeiger. Einige sagen halb im Scherz, dass geteilte Zeiger für Leute sind, die keine Systeme entwerfen können - urteilen Sie selbst.

Suchen Sie auch nach schwachen Zeigern, um ein wesentliches Gegenstück zu gemeinsamen Zeigern zu erhalten.

32
Iraimbilanja

Intelligente Zeiger bereinigen sich selbst, nachdem sie den Gültigkeitsbereich verlassen haben (wodurch die Angst vor den meisten Speicherlecks beseitigt wird). Gemeinsame Zeiger sind intelligente Zeiger, die angeben, wie viele Instanzen des Zeigers vorhanden sind. Sie bereinigen den Speicher nur, wenn die Anzahl Null erreicht. Verwenden Sie im Allgemeinen nur gemeinsam genutzte Zeiger (achten Sie jedoch darauf, dass Sie die richtige Art verwenden - für Arrays gibt es eine andere). Sie haben viel mit RAII zu tun.

21
Sydius

Um Speicherverluste zu vermeiden, können Sie Smart Pointer verwenden, wann immer Sie können. Grundsätzlich gibt es in C++ zwei verschiedene Arten von intelligenten Zeigern

  • Referenzzählung (z. B. boost :: shared_ptr/std :: tr1: shared_ptr)
  • ohne Referenzzählung (z. B. boost :: scoped_ptr/std :: auto_ptr)

Der Hauptunterschied besteht darin, dass Smart Pointer mit Referenzzählung kopiert (und in std :: -Containern verwendet) werden können, scoped_ptr jedoch nicht. Zeiger ohne Referenzzählung haben fast keinen oder gar keinen Overhead. Die Referenzzählung bringt immer eine Art Overhead mit sich.

(Ich schlage vor, auto_ptr zu vermeiden, da es bei falscher Verwendung einige schwerwiegende Fehler aufweist.)

8
d0k

Um die Antwort von Sydius ein wenig zu ergänzen, bieten intelligente Zeiger häufig eine stabilere Lösung, indem viele leicht zu behebende Fehler abgefangen werden. Rohe Zeiger haben einige Leistungsvorteile und können unter bestimmten Umständen flexibler sein. Möglicherweise müssen Sie beim Verknüpfen mit bestimmten Bibliotheken von Drittanbietern auch unformatierte Zeiger verwenden.

5
SmacL