Ich habe gerade die "Going Native 2012" -Streams angesehen und die Diskussion darüber bemerkt std::shared_ptr
. Ich war ein bisschen überrascht, als ich Bjarnes etwas negative Meinung std::shared_ptr
und seine Bemerkung hörte, dass es als "letzter Ausweg" verwendet werden sollte, wenn die Lebensdauer eines Objekts ungewiss ist (was seiner Meinung nach selten der Fall sein sollte).
Würde es jemand interessieren, dies etwas ausführlicher zu erklären? Wie können wir programmieren, ohne std::shared_ptr
die Lebensdauer von Objekten sicher zu verwalten?
c++
c++11
smart-pointer
Ronag
quelle
quelle
Antworten:
Wenn Sie das gemeinsame Eigentum vermeiden können, ist Ihre Anwendung einfacher und verständlicher und daher weniger anfällig für Fehler, die während der Wartung auftreten. Komplexe oder unklare Eigentumsmodelle führen dazu, dass es schwierig ist, Kopplungen verschiedener Teile der Anwendung durch einen gemeinsamen Status zu verfolgen, der möglicherweise nicht einfach zu verfolgen ist.
In Anbetracht dessen ist es vorzuziehen, Objekte mit automatischer Speicherdauer zu verwenden und "Wert" -Unterobjekte zu haben. Gelingt dies nicht, ist dies
unique_ptr
möglicherweise eine gute Alternative, dashared_ptr
es - wenn nicht das letzte Mittel - auf der Liste der erstrebenswerten Tools auftaucht.quelle
Die Welt, in der Bjarne lebt, ist sehr ... akademisch, aus Mangel an einem besseren Begriff. Wenn Ihr Code so entworfen und strukturiert werden kann, dass Objekte über sehr bewusste relationale Hierarchien verfügen, sodass Eigentumsverhältnisse starr und unnachgiebig sind, fließt der Code in eine Richtung (von der oberen zur unteren Ebene), und Objekte kommunizieren nur mit denjenigen, die sich in der unteren Ebene befinden die Hierarchie, dann werden Sie nicht viel brauchen
shared_ptr
. Es ist etwas, das Sie in den seltenen Fällen verwenden, in denen jemand gegen die Regeln verstoßen muss. Ansonsten können Sie einfach alles invector
s oder andere Datenstrukturen einfügen, die Wertesemantik verwenden, undunique_ptr
s für Dinge, die Sie einzeln zuweisen müssen.Es ist zwar eine großartige Welt, in der man leben kann, aber nicht das, was man die ganze Zeit tun muss. Wenn Sie nicht können Ihren Code auf diese Weise organisieren, weil das Design des Systems Sie versuchen zu machen , bedeutet , dass es unmöglich ist (oder einfach nur tief unangenehm), dann wirst du finden sich gemeinsame Verantwortung für Objekte benötigen mehr und mehr .
In einem solchen System ist das Halten nackter Zeiger ... nicht gerade gefährlich, wirft jedoch Fragen auf. Das Tolle daran
shared_ptr
ist, dass es angemessene syntaktische Garantien für die Lebensdauer des Objekts bietet . Kann es kaputt sein? Na sicher. Menschen können aber auchconst_cast
Dinge; Grundversorgung und Fütterungshared_ptr
sollten eine angemessene Lebensqualität für zugewiesene Objekte gewährleisten, deren Eigentum geteilt werden muss.Dann gibt es
weak_ptr
s, die ohne a nicht verwendet werden könnenshared_ptr
. Wenn Ihr System starr strukturiert ist, können Sie einen nackten Zeiger auf ein Objekt speichern, in dem Wissen, dass die Struktur der Anwendung sicherstellt, dass das Objekt, auf das Sie zeigen, Sie überlebt. Sie können eine Funktion aufrufen, die einen Zeiger auf einen internen oder externen Wert zurückgibt (z. B. ein Objekt mit dem Namen X suchen). In richtig strukturiertem Code steht Ihnen diese Funktion nur zur Verfügung, wenn die Lebensdauer des Objekts garantiert Ihre eigene überschreitet. Daher ist es in Ordnung, diesen nackten Zeiger in Ihrem Objekt zu speichern.Da diese Steifigkeit in realen Systemen nicht immer erreicht werden kann, müssen Sie die Lebensdauer auf angemessene Weise sicherstellen . Manchmal brauchen Sie kein vollständiges Eigentum. Manchmal muss man nur wissen können, wann der Zeiger schlecht oder gut ist. Das ist , wo
weak_ptr
kommt in . Es gibt Fälle, wo ich könnte ein verwendet haben ,unique_ptr
oderboost::scoped_ptr
, aber ich hatte eine verwenden ,shared_ptr
weil ich speziell einen „flüchtigen“ Zeiger zu geben , jemand benötigen. Ein Zeiger, dessen Lebensdauer unbestimmt war, konnte abfragen, wann dieser Zeiger zerstört wurde.Ein sicherer Weg, um zu überleben, wenn der Zustand der Welt unbestimmt ist.
Könnte das durch einen Funktionsaufruf gemacht worden sein, um den Zeiger anstatt über zu erhalten
weak_ptr
? Ja, aber das könnte leichter kaputt gehen. Eine Funktion, die einen nackten Zeiger zurückgibt, kann syntaktisch nicht vorschlagen, dass der Benutzer diesen Zeiger nicht langfristig speichert. Die Rücksendung von ashared_ptr
macht es auch viel zu einfach für jemanden, es einfach zu speichern und möglicherweise die Lebensdauer eines Objekts zu verlängern. Die Rückgabe einesweak_ptr
jedoch deutet stark darauf hin, dass das Speichern dershared_ptr
Daten, von denen Sie erhalten,lock
eine ... zweifelhafte Idee ist. Es wird Sie nicht davon abhalten, es zu tun, aber nichts in C ++ hält Sie davon ab, Code zu brechen.weak_ptr
bietet einen minimalen Widerstand gegen das Natürliche.Das soll nicht heißen, dass
shared_ptr
man es nicht überbeanspruchen kann . es kann sicherlich. Vor allem vorhinunique_ptr
gab es viele Fälle, in denen ich einfach a verwendet habe,boost::shared_ptr
weil ich einen RAII-Zeiger herumreichen oder in eine Liste aufnehmen musste. Ohne Bewegungssemantik undunique_ptr
,boost::shared_ptr
war die einzig wahre Lösung.Und Sie können es an Stellen verwenden, an denen es nicht erforderlich ist. Wie oben angegeben, kann eine ordnungsgemäße Codestruktur die Notwendigkeit einiger Verwendungen von beseitigen
shared_ptr
. Aber wenn Ihr System nicht als solches strukturiert werden kann und dennoch das tut, was es benötigt,shared_ptr
ist dies von erheblichem Nutzen.quelle
shared_ptr
eignet sich hervorragend für Systeme, in denen C ++ in Skriptsprachen wie Python integriert ist. Dieboost::python
Referenzzählung auf der C ++ - und Python-Seite funktioniert bei der Verwendung sehr gut. Jedes Objekt aus C ++ kann weiterhin in Python gespeichert werden und stirbt nicht.shared_ptr
. Beide verwenden ihre eigenen Implementierungen vonintrusive_ptr
. Ichshared_ptr
gleichermaßen gilt fürintrusive_ptr
: Er lehnt das gesamte Konzept der gemeinsamen Eigentümerschaft ab, nicht eine bestimmte Schreibweise des Konzepts. Für die Zwecke dieser Frage sind dies zwei reale Beispiele für große Anwendungen, die verwendet werdenshared_ptr
. (Und außerdem zeigen sie, dass diesshared_ptr
nützlich ist, auch wenn es nicht möglich istweak_ptr
.)Ich glaube nicht, dass ich jemals benutzt habe
std::shared_ptr
.Meistens ist ein Objekt mit einer Sammlung verbunden, zu der es während seiner gesamten Lebensdauer gehört. In diesem Fall können Sie einfach
whatever_collection<o_type>
oder verwendenwhatever_collection<std::unique_ptr<o_type>>
, wobei diese Auflistung Mitglied eines Objekts oder einer automatischen Variablen ist. Wenn Sie keine dynamische Anzahl von Objekten benötigen, können Sie natürlich auch ein automatisches Array mit fester Größe verwenden.Weder die Iteration durch die Auflistung noch eine andere Operation für das Objekt erfordert eine Hilfsfunktion, um den Besitz gemeinsam zu nutzen. Sie verwendet das Objekt und kehrt dann zurück. Der Aufrufer garantiert, dass das Objekt für den gesamten Aufruf am Leben bleibt . Dies ist mit Abstand der am häufigsten genutzte Vertrag zwischen Anrufer und Angerufenen.
Nicol Bolas kommentierte: "Wenn sich ein Objekt an einem nackten Zeiger festhält und dieser Gegenstand stirbt ... hoppla." und "Objekte müssen sicherstellen, dass das Objekt das Leben des Objekts durchlebt. Nur das
shared_ptr
kann es."Ich kaufe dieses Argument nicht. Zumindest nicht das
shared_ptr
löst dieses Problem. Wie wäre es mit:Wie bei der Garbage Collection wird
shared_ptr
der Programmierer durch die Standardverwendung von dazu angeregt, nicht über den Vertrag zwischen Objekten oder zwischen Funktion und Aufrufer nachzudenken. Es ist notwendig, über die richtigen Vor- und Nachbedingungen nachzudenken, und die Objektlebensdauer ist nur ein winziges Stück dieser größeren Torte.Objekte "sterben" nicht, ein Teil des Codes zerstört sie. Und
shared_ptr
das Problem anzugehen, anstatt den Anrufvertrag herauszufinden, ist eine falsche Sicherheit.quelle
shared_ptr
undweak_ptr
wurde entwickelt, um zu vermeiden. Bjarne versucht, in einer Welt zu leben, in der alles ein schönes, explizites Leben hat, und alles ist darauf aufgebaut. Und wenn du diese Welt bauen kannst, großartig. Aber so ist es nicht in der realen Welt. Objekte müssen sicherstellen, dass das Objekt das Leben des Objekts durchlebt. Nurshared_ptr
kann das tun.shared_ptr
mildert nur eine bestimmte äußere Modifikation und nicht einmal die häufigste. Und es liegt nicht in der Verantwortung des Objekts, sicherzustellen, dass seine Lebensdauer korrekt ist, wenn der Funktionsaufrufvertrag etwas anderes bestimmt.unique_ptr
, der ausdrückt, dass nur ein Zeiger auf das Objekt vorhanden ist und Eigentümer ist.shared_ptr
, sollte es trotzdem ein zurückgebenunique_ptr
. Die Konvertierung vonunique_ptr
nachshared_ptr
ist einfach, aber das Gegenteil ist logisch unmöglich.Ich ziehe es vor, nicht absolut zu denken (wie "letzter Ausweg"), sondern relativ zur Problemdomäne.
C ++ bietet eine Reihe von Möglichkeiten zur Verwaltung der Lebensdauer. Einige von ihnen versuchen, die Objekte auf stapelgesteuerte Weise wiederzuerlangen. Einige andere versuchen, sich dieser Einschränkung zu entziehen. Einige von ihnen sind "wörtlich", andere sind Annäherungen.
Eigentlich können Sie:
Person
eine gleiche mitname
der gleichen Person (besser: zwei Darstellung einer gleichen Person ). Lebensdauer wird vom Maschinenstapel, Ende -essentially- deosn't Angelegenheit des Programm gewährt (weil eine Person ist es s Name , egal , wasPerson
es trägt)std::unique_ptr
(Sie können sich das als einen Vektor mit Größe 1 vorstellen). Wieder geben Sie zu, dass das Objekt vor (nach) der Datenstruktur, auf die es verweist, zu existieren beginnt (und ihre Existenz beendet).Die Schwäche dieser Methoden besteht darin, dass sich Objekttypen und -mengen während der Ausführung von Aufrufen auf tieferer Stapelebene in Bezug auf den Ort, an dem sie erstellt wurden, nicht unterscheiden können. Alle diese Techniken "scheitern" an ihrer Stärke in allen Situationen, in denen das Erstellen und Löschen von Objekten auf Benutzeraktivitäten zurückzuführen ist, so dass der Laufzeit-Typ des Objekts zur Kompilierungszeit nicht bekannt ist und es zu Überstrukturen kommen kann, die auf Objekte verweisen Benutzer bittet, aus einem tieferen Funktionsaufruf auf Stapelebene zu entfernen. In diesen Fällen müssen Sie entweder:
C ++ isteslf hat keinen systemeigenen Mechanismus zum Überwachen dieses Ereignisses (
while(are_they_needed)
), daher müssen Sie ungefähr mit:Bei der allerersten bis zur letzten Lösung nimmt die Menge der für die Verwaltung der Objektlebensdauer erforderlichen Hilfsdatenstruktur mit dem Zeitaufwand für deren Organisation und Wartung zu.
Der Garbage Collector hat Kosten, shared_ptr hat weniger, unique_ptr noch weniger und Stack-verwaltete Objekte haben nur sehr wenige.
Ist
shared_ptr
der "letzte Ausweg" ?. Nein, das ist es nicht: Die letzten Mittel sind Müllsammler.shared_ptr
ist eigentlich derstd::
letzte Ausweg vorgeschlagen. Aber kann die richtige Lösung sein, wenn Sie in der Situation sind, die ich erklärte.quelle
Die eine Sache, die Herb Sutter in einer späteren Sitzung erwähnte, ist, dass jedes Mal, wenn Sie eine kopieren
shared_ptr<>
, ein ineinandergreifendes Inkrement / Dekrement auftreten muss. Bei Multithread-Code auf einem Multi-Core-System ist die Speichersynchronisation nicht unerheblich. Wenn Sie die Wahl haben, ist es besser, entweder einen Stapelwert oder einen zu verwendenunique_ptr<>
und Referenzen oder rohe Zeiger zu übergeben.quelle
shared_ptr
lvalue oder rvalue Referenz übergeben ...shared_ptr
als wäre es die Silberkugel, die all Ihre Speicherleckprobleme löst, nur weil es im Standard enthalten ist. Es ist eine verlockende Falle, aber es ist immer noch wichtig, sich des Eigentums an Ressourcen bewusst zu sein. Wenn dieses Eigentum nicht geteilt wird,shared_ptr<>
ist a nicht die beste Option.Ich erinnere mich nicht, ob der letzte "Ausweg" das genaue Wort war, das er benutzte, aber ich glaube, dass die tatsächliche Bedeutung dessen, was er sagte, die letzte "Wahl" war: Angesichts klarer Besitzverhältnisse; unique_ptr, weak_ptr, shared_ptr und sogar nackte Zeiger haben ihren Platz.
Sie waren sich einig, dass wir uns alle (Entwickler, Buchautoren usw.) in der "Lernphase" von C ++ 11 befinden und Muster und Stile definiert werden.
Als Beispiel, erklärte Herb, sollten wir neue Ausgaben einiger der wichtigsten C ++ - Bücher erwarten, wie z. B. Effective C ++ (Meyers) und C ++ - Coding-Standards (Sutter & Alexandrescu), die einige Jahre nach der Erfahrung und den Best Practices der Branche mit C liegen ++ 11 schwenkt aus.
quelle
Ich denke, was er damit meint, ist, dass es üblich wird, shared_ptr immer dann zu schreiben, wenn sie einen Standardzeiger geschrieben haben (wie eine Art globaler Ersatz), und dass es als Copout verwendet wird, anstatt tatsächlich zu entwerfen oder zumindest Planung für das Anlegen und Löschen von Objekten.
Das andere, was die Leute vergessen (abgesehen von dem oben erwähnten Engpass beim Sperren / Aktualisieren / Entsperren), ist, dass shared_ptr alleine keine Zyklusprobleme löst. Sie können mit shared_ptr weiterhin Ressourcen verlieren:
Objekt A enthält einen gemeinsamen Zeiger auf ein anderes Objekt. Objekt B erstellt A a1 und A a2 und weist die Werte a1.otherA = a2 zu. und a2.otherA = a1; Die gemeinsamen Zeiger von Objekt B, die zum Erstellen von a1, a2 verwendet wurden, haben den Gültigkeitsbereich verlassen (z. B. am Ende einer Funktion). Jetzt haben Sie ein Leck - niemand anderes bezieht sich auf a1 und a2, aber sie beziehen sich aufeinander, sodass ihre Ref-Zählungen immer 1 sind und Sie ein Leck haben.
Das ist das einfache Beispiel, wenn dies in echtem Code auftritt, geschieht dies normalerweise auf komplizierte Weise. Es gibt eine Lösung für weak_ptr, aber so viele Leute machen jetzt einfach shared_ptr überall und kennen nicht einmal das Leckproblem oder auch nur weak_ptr.
Um es zusammenzufassen: Ich denke, die Kommentare, auf die sich das OP bezieht, beschränken sich auf Folgendes:
Unabhängig davon, in welcher Sprache Sie arbeiten (verwaltet, nicht verwaltet oder etwas dazwischen mit Referenzzählungen wie shared_ptr), müssen Sie die Objekterstellung, -lebensdauer und -zerstörung verstehen und absichtlich entscheiden.
edit: auch wenn das "unbekannt" bedeutet, muss ich einen shared_ptr verwenden, du hast immer noch darüber nachgedacht und tust es absichtlich.
quelle
Ich werde aus meiner Erfahrung mit Objective-C antworten, einer Sprache, in der alle Objekte auf dem Heap referenziert und zugeordnet werden. Für den Programmierer ist es viel einfacher, Objekte auf eine Weise zu behandeln. Dadurch konnten Standardregeln definiert werden, die bei Einhaltung der Regeln Code-Robustheit und keine Speicherverluste gewährleisten. Es war auch möglich, dass clevere Compiler-Optimierungen wie die aktuelle ARC (Automatic Reference Counting) auftauchten.
Mein Punkt ist, dass shared_ptr Ihre erste Option und nicht der letzte Ausweg sein sollte. Verwenden Sie die Referenzzählung und andere Optionen nur, wenn Sie sicher sind, was Sie tun. Sie sind produktiver und Ihr Code ist robuster.
quelle
Ich werde versuchen, die Frage zu beantworten:
In C ++ gibt es eine große Anzahl von verschiedenen Möglichkeiten, um Speicher zu erstellen, zum Beispiel:
struct A { MyStruct s1,s2; };
anstelle von shared_ptr im Klassenbereich. Dies gilt nur für fortgeschrittene Programmierer, da Sie wissen müssen, wie Abhängigkeiten funktionieren, und die Fähigkeit benötigen, Abhängigkeiten so weit zu steuern, dass sie auf einen Baum beschränkt sind. Die Reihenfolge der Klassen in der Headerdatei ist dabei ein wichtiger Aspekt. Es scheint, dass diese Verwendung bereits bei systemeigenen C ++ - Typen üblich ist, aber die Verwendung mit vom Programmierer definierten Klassen scheint aufgrund dieser Abhängigkeit und der Reihenfolge der Klassenprobleme weniger häufig zu sein. Diese Lösung hat auch Probleme mit sizeof. Programmierer sehen Probleme darin als eine Anforderung, Forward-Deklarationen oder unnötige # include-Anweisungen zu verwenden, und daher greifen viele Programmierer auf eine minderwertige Lösung von Zeigern und später auf shared_ptr zurück.MyClass &find_obj(int i);
+ clone () anstelle vonshared_ptr<MyClass> create_obj(int i);
. Viele Programmierer möchten Fabriken zum Erstellen neuer Objekte erstellen. shared_ptr ist ideal für diese Art der Nutzung geeignet. Das Problem ist, dass es bereits eine komplexe Speicherverwaltungslösung mit Heap / Free Store Allocation anstelle einer einfacheren stapel- oder objektbasierten Lösung annimmt. Eine gute C ++ - Klassenhierarchie unterstützt alle Speicherverwaltungsschemata, nicht nur eines davon. Die referenzbasierte Lösung kann funktionieren, wenn das zurückgegebene Objekt im enthaltenen Objekt gespeichert ist, anstatt die lokale Funktionsbereichsvariable zu verwenden. Die Weitergabe des Eigentums von der Fabrik an den Benutzercode sollte vermieden werden. Das Kopieren des Objekts nach der Verwendung von find_obj () ist eine gute Möglichkeit, damit umzugehen - normale Kopierkonstruktoren und normale Konstruktoren (verschiedener Klassen) mit dem Referenzparameter oder clone () für polymorphe Objekte können damit umgehen.quelle
unique_ptr
ist am besten für Fabriken geeignet. Sie können eineunique_ptr
in eine verwandelnshared_ptr
, aber es ist logischerweise unmöglich, in die andere Richtung zu gehen.