it-swarm.com.de

Welche Art von Zeiger verwende ich wann?

Ok, also das letzte Mal, als ich C++ schrieb, um meinen Lebensunterhalt zu verdienen, std::auto_ptr war alles, was die Standardbibliothek zur Verfügung hatte, und boost::shared_ptr war der letzte Schrei. Ich habe mich nie wirklich mit den anderen zur Verfügung gestellten Smart Pointer-Typen beschäftigt. Ich verstehe, dass C++ 11 jetzt einige der Typen bietet, die Boost hervorgebracht hat, aber nicht alle.

Hat jemand einen einfachen Algorithmus, um zu bestimmen, wann welcher Smart Pointer verwendet werden soll? Am besten mit Hinweisen zu dummen Zeigern (rohen Zeigern wie T*) und den Rest der Boost-Smart-Pointer. (Etwas wie this wäre toll).

223
sbi

Geteilte Eigentümerschaft:
Der shared_ptr Und weak_ptr, Die vom Standard übernommen wurden, sind fast identisch mit ihren Boost-Gegenstücken . Verwenden Sie sie, wenn Sie eine Ressource teilen müssen und nicht wissen, welche als letzte am Leben sein wird. Verwenden Sie weak_ptr, Um die gemeinsam genutzte Ressource zu beobachten, ohne ihre Lebensdauer zu beeinflussen, und nicht, um Zyklen zu unterbrechen. Zyklen mit shared_ptr Sollten normalerweise nicht passieren - zwei Ressourcen können sich nicht gegenseitig besitzen.

Beachten Sie, dass Boost zusätzlich shared_array anbietet, was eine geeignete Alternative zu shared_ptr<std::vector<T> const> Sein könnte.

Als nächstes bietet Boost intrusive_ptr , eine einfache Lösung, wenn Ihre Ressource bereits eine Verwaltung mit Referenzzählung bietet und Sie diese auf das RAII-Prinzip anwenden möchten. Dieser wurde vom Standard nicht übernommen.

Einzigartiges Eigentum:
Boost hat auch ein scoped_ptr , das nicht kopierbar ist und für das Sie kein Deleter angeben können. std::unique_ptr Ist boost::scoped_ptr Für Steroide und sollte Ihre Standardeinstellung sein, wenn Sie einen intelligenten Zeiger benötigen. Es erlaubt Ihnen, ein Deleter in seinen Template-Argumenten anzugeben und ist beweglich , im Gegensatz zu boost::scoped_ptr. Es ist auch in STL-Containern voll verwendbar, solange Sie keine Operationen verwenden, die (offensichtlich) kopierbare Typen benötigen.

Beachten Sie noch einmal, dass Boost eine Array-Version hat: scoped_array , die der Standard vereinheitlicht, indem std::unique_ptr<T[]> Eine teilweise Spezialisierung erfordert, die den Zeiger statt delete[]deleteing es (mit dem default_delete r). std::unique_ptr<T[]> Bietet auch operator[] Anstelle von operator* Und operator-> An.

Beachten Sie, dass std::auto_ptr Immer noch im Standard enthalten ist, aber nicht mehr unterstützt wird . §D.10 [depr.auto.ptr]

Die Klassenvorlage auto_ptr Ist veraltet. [ Hinweis: Die Klassenvorlage unique_ptr (20.7.1) bietet eine bessere Lösung. - Endnote ]

Kein Besitz:
Verwenden Sie dumme Zeiger (rohe Zeiger) oder Verweise auf nicht im Besitz befindliche Verweise auf Ressourcen, und wenn Sie wissen, dass die Die Ressource überlebt das referenzierende Objekt/den referenzierenden Bereich. Bevorzugen Sie Verweise und verwenden Sie unformatierte Zeiger, wenn Sie eine Null- oder Rücksetzbarkeit benötigen.

Wenn Sie einen nicht im Besitz befindlichen Verweis auf eine Ressource wünschen, aber nicht wissen, ob die Ressource das Objekt, das auf sie verweist, überlebt, packen Sie die Ressource in einen shared_ptr Und verwenden Sie einen weak_ptr - Sie können testen, ob das übergeordnete Element shared_ptr mit lock am Leben ist. Dies gibt ein shared_ptr zurück, das nicht null ist, wenn die Ressource noch vorhanden ist. Wenn Sie testen möchten, ob die Ressource tot ist, verwenden Sie expired. Die beiden mögen sich ähnlich anhören, sind jedoch bei gleichzeitiger Ausführung sehr unterschiedlich, da expired nur den Rückgabewert für diese einzelne Anweisung garantiert. Ein scheinbar unschuldiger Test wie

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

ist eine potenzielle Rennbedingung.

177
Xeo

Die Entscheidung, welcher Smart Pointer verwendet werden soll, ist eine Frage von Eigentumsrecht. Wenn es um die Ressourcenverwaltung geht, ist Objekt A besitzt Objekt B, wenn es die Lebensdauer von Objekt B steuert. Beispielsweise gehören Mitgliedsvariablen ihren jeweiligen Objekten, da die Lebensdauer von Mitgliedsvariablen gebunden ist auf die Lebensdauer des Objekts. Sie wählen intelligente Zeiger basierend auf dem Besitz des Objekts.

Beachten Sie, dass das Eigentum an einem Softwaresystem vom Eigentum getrennt ist, wie wir es außerhalb von Software betrachten würden. Zum Beispiel könnte eine Person ihr Haus "besitzen", aber das bedeutet nicht unbedingt, dass ein Person - Objekt die Kontrolle über die Lebensdauer eines House - Objekts hat. Wenn Sie diese Konzepte der realen Welt mit Softwarekonzepten kombinieren, können Sie sich auf sichere Weise selbst in ein Loch programmieren.


Wenn Sie das alleinige Eigentum an dem Objekt haben, verwenden Sie std::unique_ptr<T>.

Wenn Sie das Eigentum an dem Objekt geteilt haben ...
- Wenn sich keine Zyklen im Besitz befinden, verwenden Sie std::shared_ptr<T>.
- Wenn es Zyklen gibt, definieren Sie eine "Richtung" und verwenden Sie std::shared_ptr<T> in eine Richtung und std::weak_ptr<T> in dem anderen.

Wenn das Objekt Ihnen gehört, Sie aber möglicherweise keinen Eigentümer haben, verwenden Sie normale Zeiger T* (z. B. übergeordnete Zeiger).

Wenn das Objekt Sie besitzt (oder anderweitig eine garantierte Existenz hat), verwenden Sie Referenzen T&.


Vorsichtsmaßnahme: Beachten Sie die Kosten für intelligente Zeiger. In speicher- oder leistungsbeschränkten Umgebungen kann es vorteilhaft sein, nur normale Zeiger mit einem manuelleren Schema für die Speicherverwaltung zu verwenden.

Die Kosten:

  • Wenn Sie einen benutzerdefinierten Löscher haben (z. B. Sie verwenden Zuordnungspools), entsteht ein Overhead pro Zeiger, der durch manuelles Löschen leicht vermieden werden kann.
  • std::shared_ptr hat den Overhead eines Referenzzählungsinkrements beim Kopieren, plus eines Dekrements beim Zerstören, gefolgt von einer 0-Zählungsprüfung mit Löschen des gehaltenen Objekts. Abhängig von der Implementierung kann dies Ihren Code aufblähen und Leistungsprobleme verursachen.
  • Kompilierzeit. Wie bei allen Vorlagen wirken sich intelligente Zeiger negativ auf die Kompilierzeiten aus.

Beispiele:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

Ein binärer Baum besitzt keinen übergeordneten Baum, aber die Existenz eines Baums impliziert die Existenz seines übergeordneten Baums (oder nullptr für root), sodass ein normaler Zeiger verwendet wird. Ein binärer Baum (mit Wertsemantik) hat das alleinige Eigentum seiner Kinder, das sind also std::unique_ptr.

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

Hier besitzt der Listenknoten seine nächste und vorherige Liste, also definieren wir eine Richtung und verwenden shared_ptr für next und weak_ptr for prev, um den Zyklus zu unterbrechen.

127
Peter Alexander

Verwenden Sie die ganze Zeit unique_ptr<T>, Es sei denn, Sie benötigen eine Referenzzählung. In diesem Fall verwenden Sie shared_ptr<T> (Und in sehr seltenen Fällen weak_ptr<T>, Um Referenzzyklen zu verhindern). In fast allen Fällen ist übertragbares Alleinstellungsmerkmal in Ordnung.

Rohe Zeiger: Nur gut, wenn Sie kovariante Renditen benötigen und keine eigenen Zeiger, die auftreten können. Ansonsten sind sie nicht besonders nützlich.

Array-Zeiger: unique_ptr Hat eine Spezialisierung für T[], Die automatisch delete[] Für das Ergebnis aufruft, sodass Sie beispielsweise sicher unique_ptr<int[]> p(new int[42]); ausführen können. shared_ptr Sie benötigen immer noch einen benutzerdefinierten Löscher, aber keinen speziellen gemeinsamen oder eindeutigen Array-Zeiger. Natürlich werden solche Dinge normalerweise sowieso am besten durch std::vector Ersetzt. Leider bietet shared_ptr Keine Array-Zugriffsfunktion, sodass Sie get() immer noch manuell aufrufen müssen, aber unique_ptr<T[]> Bietet operator[] Anstelle von operator* Und operator->. In jedem Fall muss man sich grenzenlos kontrollieren. Dies macht shared_ptr Etwas weniger benutzerfreundlich, obwohl der allgemeine Vorteil und die fehlende Abhängigkeit von Boosts unique_ptr Und shared_ptr Erneut zu den Gewinnern machen.

Bereichsspezifische Zeiger: Durch unique_ptr Irrelevant gemacht, genau wie auto_ptr.

Da ist wirklich nichts mehr dran. In C++ 03 ohne Verschiebungssemantik war diese Situation sehr kompliziert, aber in C++ 11 ist der Rat sehr einfach.

Es gibt noch Verwendungen für andere intelligente Zeiger wie intrusive_ptr Oder interprocess_ptr. Sie sind jedoch sehr Nischen und im allgemeinen Fall völlig unnötig.

19
Puppy

Fälle, in denen unique_ptr Verwendet werden soll:

  • Fabrik methoden
  • Mitglieder, die Zeiger sind (Pickel enthalten)
  • Speichern von Zeigern in STL-Containern (um Bewegungen zu vermeiden)
  • Verwendung großer lokaler dynamischer Objekte

Fälle, in denen shared_ptr Verwendet werden soll:

  • Objekte über Threads hinweg teilen
  • Objekte im Allgemeinen teilen

Fälle, in denen weak_ptr Verwendet werden soll:

  • Große Karte, die als allgemeine Referenz dient (z. B. eine Karte aller offenen Sockets)

Fühlen Sie sich frei, um mehr zu bearbeiten und hinzuzufügen

7
Lalaland