Würde im Nachhinein make_shared
, shared_ptr
einen Konstruktor haben, der einen rohen Zeiger benutzt, wenn er mit C ++ 11 eingeführt worden wäre?
Gibt es starke Argumente oder Anwendungsfälle zugunsten dieses Konstruktors?
Es hätte die gut dokumentierte Falle der Ausnahmesicherheit und der Speicherzuweisung / Leistungsvorteil der Verwendung von make_shared
.
Ich glaube, ein weiterer Vorteil von shared_ptr
construction via make_shared
wäre, dass es ein einzelner Zeiger unter der Haube sein könnte, der den Speicherverbrauch senkt und Dinge wie atomic_compare_exchange viel einfacher (und möglicherweise effizienter). (Siehe Präsentation von C ++ jetzt )
BEARBEITEN
Ich verstehe, dass ein shared_ptr, das im Grunde genommen ein intrusiver_ptr ist (mit dem Objekt und dem Kontrollblock koalesziert), keine Eigenschaften hätte, die der aktuelle std :: shared_ptr hat. Wie:
die Fähigkeit, das Objekt getrennt von dem Kontrollblock zu befreien (was schön ist, wenn Sie weak_ptrs schon lange gelebt haben)
Kompatibilität mit Bibliotheken, die Ihnen rohe Zeiger und die Verantwortung geben, sie zu befreien
die Fähigkeit, beliebige Ressourcen mit benutzerdefinierten Deletern zu halten (oder keine Delete, für nicht besitzende Zeiger)
die Fähigkeit, auf ein Unterobjekt (z. B. ein Mitglied) zu zeigen, während das übergeordnete Objekt am Leben erhalten wird.
Was ich vorschlage, ist, dass diese Features möglicherweise nicht häufig genug verwendet werden (oder im Falle der Verwendung als Rai-Wrapper), möglicherweise nicht die beste Lösung sind, um die zusätzlichen Kosten zu rechtfertigen:
In einer C ++ 98-Welt (wo shared_ptr eingeführt wurde) ist make_shared weniger praktisch und weniger benutzerfreundlich (das Fehlen einer perfekten Weiterleitung erfordert Referenzwrapper und das Fehlen von variablen Vorlagen macht die Implementierung klobig).
Das Problem mit Ihrer Logik ist die Annahme, dass der Grund, warum shared_ptr
zwischen dem verwalteten Zeiger und dem get
-Zeiger unterscheidet, darin liegt, dass make_shared
nicht verfügbar war. Wenn wir also alle dazu zwingen, make_shared
zu verwenden, um shared_ptr
zu erstellen, brauchen wir diese Unterscheidung nicht.
Das ist falsch.
Sie können den zeigerbasierten Konstruktor von shared_ptr
ohne diese Unterscheidung implementieren. Bei der anfänglichen Erstellung eines verwalteten shared_ptr
sind der get
-Zeiger und der verwaltete Zeiger identisch. Wenn Sie möchten, dass shared_ptr
die sizeof(T*)
ist, können Sie den shared_ptr
den Zeiger get
aus dem verwalteten Block holen. Dies ist unabhängig davon, ob T
in den verwalteten Block eingebettet ist.
Also hat die Unterscheidung wirklich gar nichts zu tun mit make_shared
und ihrer Fähigkeit, das T
in den gleichen Speicher wie den verwalteten Block einzubetten. Oder eher, das Fehlen davon.
Nein, die Unterscheidung zwischen dem verwalteten Zeiger und dem get
-Zeiger wurde erstellt, weil Features zu shared_ptr
hinzugefügt wurden. Wichtige. Sie haben einige aufgelistet, aber Sie haben andere vermisst:
Die Fähigkeit, eine shared_ptr
zu einer Basisklasse zu haben. Das ist:
Dazu müssen Sie unterscheiden zwischen dem, worauf eine bestimmte Instanz verweist und was der Steuerblock steuert.
static_pointer_cast
und dynamic_pointer_cast
(und reinterpret_pointer_cast
in C ++ 17). Sie alle basieren auf der Unterscheidung zwischen dem verwalteten Zeiger und dem get
-Zeiger.
enable_shared_from_this
in Basisklassen. A shared_ptr
, die auf ein Mitgliedsteilobjekt eines Typs verweist, der selbst von einem shared_ptr
verwaltet wird. Auch hier muss der verwaltete Zeiger nicht mit dem get
-Zeiger übereinstimmen.
Sie scheinen auch die Möglichkeit zu vernachlässigen, Zeiger zu verwalten, die nicht von Ihnen erstellt wurden. Das ist eine kritische Fähigkeit, weil Sie damit kompatibel zu anderen Codebasen sind. Intern können Sie shared_ptr
verwenden, um Dinge zu verwalten, die von einer Bibliothek erstellt wurden, die 1998 geschrieben wurde.
Mit Ihrem Weg teilen Sie Code in zwei Epochen: Pre-C ++ 11 und Post-C ++ 11. Ihr shared_ptr
wird nichts für irgendeinen Code tun, der nicht explizit für C ++ 11 geschrieben wurde.
Und die Sache mit dem Umschließen aller dieser Features zu einem einzigen Typ ist:
Sie brauchen keinen anderen.
shared_ptr
, weil es so viele Bedürfnisse erfüllt, kann praktisch überall praktisch genutzt werden. Es ist vielleicht nicht der absolut effizienteste Typ, aber wird in praktisch jedem Fall die Aufgabe erfüllen. Und es ist nicht gerade langsam dabei.
Es behandelt gemeinsame Eigentumsrechte mit Polymorphismus. Es behandelt den gemeinsamen Besitz von Mitgliedsobjekten. Es behandelt den gemeinsamen Besitz von Speicher, den Sie nicht zugewiesen haben. Es behandelt den gemeinsamen Besitz von Speicher mit speziellen Zuteilungs- / Freigabe-Anforderungen. Und so weiter.
Wenn Sie Shared-Ownership-Semantiken benötigen, und Sie müssen arbeiten , hat shared_ptr
jedes Mal Ihren Rücken. Mit Ihrer vorgeschlagenen Idee würde es immer Einschränkungen geben, die von Ihrer Arbeit herrühren.
Ein Typ, der funktioniert, sollte standardmäßig vor einem anderen bevorzugt werden.
Würde im Nachhinein
make_shared
angegeben, hätteshared_ptr
einen Konstruktor, der einen rohen Zeiger benutzt, wenn er mit C ++ 11 eingeführt worden wäre?
Was passiert, wenn Sie die Zuweisung des Objekts nicht kontrollieren? Was ist, wenn Sie einen benutzerdefinierten Deleter verwenden müssen? Was ist, wenn Sie eine Listeninitialisierung anstelle von Parens benötigen?
Keiner dieser Fälle wird von make_shared
verarbeitet.
Wenn Sie weak_ptr
verwenden, wird außerdem ein shared_ptr
, der über make_shared
zugewiesen wurde, keinen Speicher freigeben, bis alle weak_ptr
s ebenfalls zerstört sind. Selbst wenn Sie einen normalen Shared-Pointer haben, wo keiner der obigen Punkte zutrifft, ist es möglich, dass Sie immer noch dem rohen Pointer-Konstruktor vorziehen.
Noch eine andere Situation wäre, wenn Ihr Typ Überladungen für operator new
und operator delete
bietet. Dies macht es möglicherweise ungeeignet für make_shared
, da diese Überladungen nicht aufgerufen werden - und vermutlich existieren sie aus einem bestimmten Grund.
std::shared_ptr
tut viel mehr als Objekte auf dem Heap zuzuweisen.
Betrachten Sie die Verwendung als automatisch schließende Dateizugriffsdatei:
%Vor%Tags und Links c++ c++11 shared-ptr make-shared