war row-pointer Konstruktor von shared_ptr ein Fehler?

8

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:

  1. die Fähigkeit, das Objekt getrennt von dem Kontrollblock zu befreien (was schön ist, wenn Sie weak_ptrs schon lange gelebt haben)

  2. Kompatibilität mit Bibliotheken, die Ihnen rohe Zeiger und die Verantwortung geben, sie zu befreien

  3. die Fähigkeit, beliebige Ressourcen mit benutzerdefinierten Deletern zu halten (oder keine Delete, für nicht besitzende Zeiger)

  4. 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:

  1. ein separater Zeiger auf den Steuerblock
  2. (möglicherweise) komplexere atomic_compare_exchange-Logik, ist es vielleicht nicht wert.

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).

    
Arvid 22.05.2016, 17:45
quelle

3 Antworten

13

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:

    %Vor%

    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.

    • Dies beinhaltet auch 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.

    
Nicol Bolas 25.05.2016, 22:22
quelle
14
  

Würde im Nachhinein make_shared angegeben, hätte shared_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.

    
Barry 22.05.2016 18:05
quelle
4

std::shared_ptr tut viel mehr als Objekte auf dem Heap zuzuweisen.

Betrachten Sie die Verwendung als automatisch schließende Dateizugriffsdatei:

%Vor%     
Richard Hodges 22.05.2016 18:18
quelle

Tags und Links