126 Stimmen

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 in Ogre3D Rendering-Engine gibt es eine tiefe Verwendung von gemeinsamen Zeigern.

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

153voto

hazzen Punkte 16604

Sydius hat die Typen recht gut umrissen:

  • Normale Zeiger sind genau das - sie verweisen auf etwas, das sich irgendwo im Speicher befindet. Wem gehört es? Das verraten nur die Kommentare. Wer gibt es frei? Irgendwann hoffentlich der Besitzer.
  • Intelligente Zeiger sind ein Sammelbegriff, der viele Typen abdeckt; ich nehme an, Sie meinten scoped pointer, der die RAII Muster. Es handelt sich um ein Stack-Objekt, das einen Zeiger umhüllt; wenn es den Gültigkeitsbereich verlässt, ruft es delete für den Zeiger auf, den es umhüllt. Es "besitzt" den enthaltenen Zeiger insofern, als es dafür verantwortlich ist, ihn zu einem bestimmten Zeitpunkt zu löschen. Sie ermöglichen es, einen Rohverweis auf den Zeiger zu erhalten, den sie umhüllen, um ihn an andere Methoden zu übergeben, sowie Freisetzung von den Zeiger, so dass er in den Besitz einer anderen Person übergeht. Es ist nicht sinnvoll, sie zu kopieren.
  • Gemeinsam genutzte Zeiger ist ein auf einem Stapel zugewiesenes Objekt, das einen Zeiger umhüllt, so dass man nicht wissen muss, wem er gehört. Wenn der letzte gemeinsam genutzte Zeiger für ein Objekt im Speicher vernichtet wird, wird auch der umhüllte Zeiger gelöscht.

Und wann sollten Sie sie einsetzen? Sie werden entweder stark von "scoped pointers" oder "shared pointers" Gebrauch machen. Wie viele Threads laufen in Ihrer Anwendung? Wenn die Antwort "potenziell viele" lautet, können sich gemeinsam genutzte Zeiger als Leistungsengpass erweisen, wenn sie überall eingesetzt werden. Der Grund dafür ist, dass das Erstellen/Kopieren/Destruieren eines gemeinsam genutzten Zeigers ein atomarer Vorgang sein muss, und das kann die Leistung beeinträchtigen, wenn viele Threads ausgeführt werden. Dies wird jedoch nicht immer der Fall sein - nur durch Testen können Sie dies mit Sicherheit feststellen.

Es gibt ein Argument (das mir gefällt) gegen gemeinsam genutzte Zeiger - wenn man sie verwendet, können Programmierer ignorieren, wem ein Zeiger gehört. Dies kann zu kniffligen Situationen mit zirkulären Verweisen führen (Java erkennt diese, aber Shared Pointer nicht) oder zu allgemeiner Faulheit der Programmierer in einer großen Codebasis.

Es gibt zwei Gründe für die Verwendung von scoped pointers. Der erste ist für einfache Ausnahmesicherheit und Bereinigungsoperationen - wenn Sie garantieren wollen, dass ein Objekt angesichts von Ausnahmen auf jeden Fall bereinigt wird, und Sie dieses Objekt nicht auf dem Stack allozieren wollen, legen Sie es in einen scoped pointer. Wenn die Operation erfolgreich ist, können Sie es in einen gemeinsamen Zeiger übertragen, aber in der Zwischenzeit sparen Sie den Overhead mit einem scoped pointer.

Der andere Fall ist, dass Sie eine klare Objektverantwortung wünschen. Manche Teams bevorzugen dies, manche nicht. Zum Beispiel kann eine Datenstruktur Zeiger auf interne Objekte zurückgeben. Bei einem "scoped pointer" würde ein "raw pointer" oder ein Verweis zurückgegeben, der als "weak reference" behandelt werden sollte - es ist ein Fehler, auf diesen Zeiger zuzugreifen, nachdem die Datenstruktur, der er gehört, zerstört wurde, und es ist ein Fehler, ihn zu löschen. Bei einem gemeinsam genutzten Zeiger kann das besitzende Objekt die internen Daten, die es zurückgegeben hat, nicht zerstören, wenn jemand noch ein Handle darauf hält - dies könnte Ressourcen viel länger als nötig offen lassen, oder je nach Code noch viel schlimmer.

34voto

Der Begriff "Smart Pointer" enthält Shared Pointers, Auto Pointers, Locking Pointers und andere. Sie meinten Auto Pointer (mehrdeutig bekannt als "Owning Pointer"), nicht Smart Pointer.

Dumme Zeiger (T*) sind nie die beste Lösung. Sie zwingen Sie zu einer expliziten Speicherverwaltung, die langwierig, fehleranfällig und manchmal nahezu unmöglich ist. Aber was noch wichtiger ist: Sie signalisieren nicht Ihre Absicht.

Auto-Pointer löschen das Pointee bei der Zerstörung. Für Arrays sind Kapselungen wie Vector und Deque vorzuziehen. Für andere Objekte gibt es sehr selten die Notwendigkeit, sie auf dem Heap zu speichern - verwenden Sie einfach Locals und Objektkomposition. Die Notwendigkeit für Auto-Pointer ergibt sich jedoch bei Funktionen, die Heap-Pointer zurückgeben - wie Fabriken und polymorphe Rückgaben.

Gemeinsam genutzte Zeiger löschen den Zeiger, wenn der letzte gemeinsam genutzte Zeiger auf ihn zerstört wird. Dies ist nützlich, wenn Sie ein einfaches, offenes Speichersystem wünschen, bei dem die erwartete Lebensdauer und der Besitz je nach Situation stark variieren können. Aufgrund der Notwendigkeit, einen (atomaren) Zähler zu führen, sind sie etwas langsamer als Auto-Pointer. Manche sagen halb im Scherz, dass Shared Pointer für Leute sind, die keine Systeme entwerfen können - urteilen Sie selbst.

Ein wesentliches Gegenstück zu Shared Pointers sind auch Weak Pointers.

21voto

Sydius Punkte 12717

Intelligente Zeiger bereinigen sich selbst, nachdem sie den Gültigkeitsbereich verlassen haben (wodurch die Angst vor den meisten Speicherlecks verschwindet). Gemeinsam genutzte Zeiger sind intelligente Zeiger, die zählen, wie viele Instanzen des Zeigers existieren, und den Speicher nur aufräumen, wenn die Zahl Null erreicht. Im Allgemeinen sollten Sie nur Shared Pointer verwenden (aber achten Sie darauf, dass Sie die richtige Art verwenden - es gibt eine andere für Arrays). Sie haben eine Menge zu tun mit RAII .

10voto

d0k Punkte 2565

Um Speicherlecks zu vermeiden, können Sie Smart Pointer verwenden, wann immer Sie können. In C++ gibt es grundsätzlich 2 verschiedene Arten von intelligenten Zeigern

  • Referenz gezählt (z. B. boost::shared_ptr / std::tr1:shared_ptr)
  • nicht referenziert (z. B. boost::scoped_ptr / std::auto_ptr)

Der Hauptunterschied besteht darin, dass referenzgezählte Smart Pointer kopiert (und in std:: Containern verwendet) werden können, scoped_ptr hingegen nicht. Nicht referenzgezählte Zeiger haben fast keinen oder gar keinen Overhead. Das Zählen von Referenzen führt immer zu irgendeinem Overhead.

(Ich schlage vor, auto_ptr zu vermeiden, es hat einige schwerwiegende Mängel, wenn falsch verwendet)

5voto

SmacL Punkte 22014

Um die Antwort von Sydius ein wenig zu ergänzen, bieten intelligente Zeiger oft eine stabilere Lösung, da sie viele leicht zu machende Fehler abfangen. Rohe Zeiger haben einige Vorteile bei der Leistung und können unter bestimmten Umständen flexibler sein. Sie können auch gezwungen sein, rohe Zeiger zu verwenden, wenn Sie in bestimmte Bibliotheken von Drittanbietern einbinden.

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