Unterschied zwischen make_shared und normal shared_ptr in C++

Lesezeit: 8 Minuten

Unterschied zwischen make shared und normal shared ptr in C
Anup Buchke

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

Viele Google- und Stackoverflow-Beiträge gibt es dazu, aber ich kann nicht verstehen, warum make_shared ist effizienter als direkt zu verwenden shared_ptr.

Kann mir jemand die Schritt-für-Schritt-Abfolge der erstellten Objekte und der von beiden durchgeführten Operationen erklären, damit ich verstehen kann, wie make_shared ist effizient. Ich habe oben ein Beispiel als Referenz gegeben.

  • Effizienter geht es nicht. Der Grund für die Verwendung ist die Ausnahmesicherheit.

    – Yuushi

    3. Januar 2014 um 2:50 Uhr

  • STL behandelte dies in einem seiner Videos auf Kanal 9. Wahrscheinlich war es so Dieses hier.

    – Chris

    3. Januar 2014 um 2:53 Uhr


  • @Yuushi: Ausnahmesicherheit ist ein guter Grund, es zu verwenden, aber es ist auch effizienter.

    – Mike Seymour

    3. Januar 2014 um 2:53 Uhr

  • Bei 32:15 beginnt er in dem Video, das ich oben verlinkt habe, falls das hilft.

    – Chris

    3. Januar 2014 um 3:02 Uhr

  • Vorteil des geringfügigen Codestils: using make_shared Du kannst schreiben auto p1(std::make_shared<A>()) und p1 hat den richtigen Typ.

    – Iwan Vergiljew

    11. Januar 2014 um 0:25 Uhr

1646817612 996 Unterschied zwischen make shared und normal shared ptr in C
parken

Der Unterschied ist das std::make_shared führt eine Heap-Zuweisung durch, während der Aufruf der std::shared_ptr Konstruktor führt zwei aus.

Wo finden die Heap-Zuweisungen statt?

std::shared_ptr verwaltet zwei Einheiten:

  • der Steuerblock (speichert Metadaten wie Ref-Counts, Type-Erased Deleter usw.)
  • das verwaltete Objekt

std::make_shared führt eine einzelne Heap-Zuordnung durch, die den Platz berücksichtigt, der sowohl für den Steuerblock als auch für die Daten erforderlich ist. Im anderen Fall new Obj("foo") ruft eine Heap-Zuweisung für die verwalteten Daten und die auf std::shared_ptr Der Konstruktor führt einen weiteren für den Steuerblock aus.

Weitere Informationen finden Sie unter Hinweise zur Umsetzung bei cpReferenz.

Update I: Ausnahme-Sicherheit

HINWEIS (2019/08/30)Hinweis: Dies ist seit C++17 kein Problem mehr, da sich die Auswertungsreihenfolge von Funktionsargumenten geändert hat. Insbesondere muss jedes Argument einer Funktion vollständig ausgeführt werden, bevor andere Argumente ausgewertet werden.

Da sich das OP anscheinend über die Ausnahmesicherheitsseite der Dinge wundert, habe ich meine Antwort aktualisiert.

Betrachten Sie dieses Beispiel,

void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")),
  std::shared_ptr<Rhs>(new Rhs("bar")));

Da C++ eine beliebige Reihenfolge der Auswertung von Teilausdrücken zulässt, ist eine mögliche Reihenfolge:

  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. std::shared_ptr<Rhs>

Nehmen wir nun an, bei Schritt 2 wird eine Ausnahme ausgelöst (z. Rhs Konstruktor hat eine Ausnahme ausgelöst). Wir verlieren dann den in Schritt 1 zugewiesenen Speicher, da nichts eine Chance hatte, ihn zu bereinigen. Der Kern des Problems hier ist, dass der Raw-Zeiger nicht an die übergeben wurde std::shared_ptr Konstrukteur sofort.

Eine Möglichkeit, dies zu beheben, besteht darin, sie in separaten Zeilen auszuführen, damit diese willkürliche Reihenfolge nicht auftreten kann.

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

Der bevorzugte Weg, dies zu lösen, ist natürlich die Verwendung std::make_shared stattdessen.

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

Update II: Nachteil von std::make_shared

Zitat von Caseys Kommentaren:

Da es nur eine Zuordnung gibt, kann der Speicher des Pointees nicht freigegeben werden, bis der Steuerblock nicht mehr verwendet wird. EIN weak_ptr kann den Kontrollblock auf unbestimmte Zeit am Leben erhalten.

Warum tun Instanzen von weak_ptrs den Kontrollblock am Leben erhalten?

Es muss doch einen Weg geben weak_ptrs, um festzustellen, ob das verwaltete Objekt noch gültig ist (z. B. für lock). Sie tun dies, indem sie die Anzahl der überprüfen shared_ptrs, die das verwaltete Objekt besitzen, das im Kontrollblock gespeichert ist. Das Ergebnis ist, dass die Steuerblöcke bis zum lebendig sind shared_ptr zählen und die weak_ptr zählen beide Treffer 0.

Zurück zu std::make_shared

Seit std::make_shared eine einzige Heap-Zuordnung sowohl für den Steuerblock als auch für das verwaltete Objekt vornimmt, gibt es keine Möglichkeit, den Speicher für den Steuerblock und das verwaltete Objekt unabhängig voneinander freizugeben. Wir müssen warten, bis wir sowohl den Kontrollblock als auch das verwaltete Objekt freigeben können, was passiert, bis es keine gibt shared_ptrs oder weak_ptrlebt.

Angenommen, wir haben stattdessen zwei Heap-Zuweisungen für den Steuerblock und das verwaltete Objekt über durchgeführt new und shared_ptr Konstrukteur. Dann geben wir den Speicher für das verwaltete Objekt frei (vielleicht früher), wenn es keinen gibt shared_ptrs am Leben, und geben Sie den Speicher für den Steuerblock (vielleicht später) frei, wenn keine vorhanden sind weak_ptrlebt.

  • Es ist eine gute Idee, den kleinen Nachteil des Eckgehäuses zu erwähnen make_shared außerdem: Da es nur eine Zuordnung gibt, kann der Speicher des Pointees nicht freigegeben werden, bis der Steuerblock nicht mehr verwendet wird. EIN weak_ptr kann den Kontrollblock auf unbestimmte Zeit am Leben erhalten.

    – Casey

    3. Januar 2014 um 21:01 Uhr


  • Ein weiterer, eher stilistischer Punkt ist: Wenn Sie verwenden make_shared und make_unique konsequent, Sie haben keine rohen Zeiger und können jedes Vorkommen behandeln new als Code-Geruch.

    – Philipp

    5. Januar 2014 um 22:04 Uhr

  • Wenn es nur einen gibt shared_ptrund nein weak_ptrs, anrufen reset() auf der shared_ptr Instanz löscht den Kontrollblock. Aber das ist egal oder ob make_shared wurde benutzt. Verwenden make_shared macht einen Unterschied, weil es die Lebensdauer von verlängern könnte der für das verwaltete Objekt zugewiesene Speicher. Wenn das shared_ptr count auf 0 trifft, wird der Destruktor für das verwaltete Objekt unabhängig davon aufgerufen make_sharedaber das Freigeben des Speichers kann nur erfolgen, wenn make_shared war nicht benutzt. Hoffe das macht es klarer.

    – parken

    6. Januar 2014 um 18:42 Uhr

  • Erwähnenswert wäre auch, dass make_shared die „We Know Where You Live“-Optimierung nutzen kann, die es ermöglicht, dass der Steuerblock einen Zeiger kleiner ist. (Einzelheiten siehe Stephan T. Lavavejs GN2012-Präsentation etwa bei Minute 12.) make_shared vermeidet also nicht nur eine Allokation, sondern allokiert auch insgesamt weniger Speicher.

    – KnowItAllMöchtegern

    28. Januar 2014 um 6:31 Uhr

  • @HannaKhalil: Ist das vielleicht das Reich dessen, wonach du suchst…? melpon.org/wandbox/permlink/b5EpsiSxDeEz8lGH

    – parken

    20. Dezember 2016 um 5:17 Uhr

Es gibt einen weiteren Fall, in dem sich die beiden Möglichkeiten zusätzlich zu den bereits erwähnten unterscheiden: Wenn Sie einen nicht öffentlichen Konstruktor (geschützt oder privat) aufrufen müssen, kann make_shared möglicherweise nicht darauf zugreifen, während die Variante mit dem neuen problemlos funktioniert .

class A
{
public:

    A(): val(0){}

    std::shared_ptr<A> createNext(){ return std::make_shared<A>(val+1); }
    // Invalid because make_shared needs to call A(int) **internally**

    std::shared_ptr<A> createNext(){ return std::shared_ptr<A>(new A(val+1)); }
    // Works fine because A(int) is called explicitly

private:

    int val;

    A(int v): val(v){}
};

  • Ich stieß auf genau dieses Problem und entschied mich für die Verwendung newsonst hätte ich verwendet make_shared. Hier ist eine verwandte Frage dazu: stackoverflow.com/questions/8147027/….

    – Pummeluff

    9. November 2018 um 3:37 Uhr

Der gemeinsame Zeiger verwaltet sowohl das Objekt selbst als auch ein kleines Objekt, das die Referenzzählung und andere Verwaltungsdaten enthält. make_shared kann einen einzigen Speicherblock zuweisen, um beide zu halten; Das Konstruieren eines gemeinsam genutzten Zeigers aus einem Zeiger auf ein bereits zugewiesenes Objekt muss einen zweiten Block zuweisen, um die Referenzzählung zu speichern.

Neben dieser Effizienz, mit make_shared bedeutet, dass Sie sich nicht damit befassen müssen new und rohe Zeiger überhaupt, was eine bessere Ausnahmesicherheit bietet – es gibt keine Möglichkeit, eine Ausnahme auszulösen, nachdem das Objekt zugewiesen wurde, aber bevor es dem intelligenten Zeiger zugewiesen wurde.

  • Deinen ersten Punkt habe ich richtig verstanden. Können Sie den zweiten Punkt zur Ausnahmesicherheit bitte näher erläutern oder einige Links geben?

    – Anup Buchke

    3. Januar 2014 um 3:16 Uhr

Ich sehe ein Problem mit std::make_shared, es unterstützt keine privaten/geschützten Konstruktoren

Wenn Sie eine spezielle Speicherausrichtung für das von shared_ptr gesteuerte Objekt benötigen, können Sie sich nicht auf make_shared verlassen, aber ich denke, es ist der einzige gute Grund, es nicht zu verwenden.

  • Eine zweite Situation, in der make_shared ungeeignet ist, ist, wenn Sie einen benutzerdefinierten Löscher angeben möchten.

    – KnowItAllMöchtegern

    28. Januar 2014 um 6:22 Uhr

1646817613 959 Unterschied zwischen make shared und normal shared ptr in C
boop_the_snoot

Shared_ptr: Führt zwei Heap-Zuweisungen durch

  1. Steuerblock (Referenzzähler)
  2. Objekt wird verwaltet

Make_shared: Führt nur eine Heap-Zuordnung durch

  1. Kontrollblock- und Objektdaten.

  • Eine zweite Situation, in der make_shared ungeeignet ist, ist, wenn Sie einen benutzerdefinierten Löscher angeben möchten.

    – KnowItAllMöchtegern

    28. Januar 2014 um 6:22 Uhr

Unterschied zwischen make shared und normal shared ptr in C
Martin Vorbrodt

Ich denke, der Teil der Ausnahmesicherheit in der Antwort von Herrn mpark ist immer noch ein berechtigtes Anliegen. Wenn Sie einen shared_ptr wie folgt erstellen: shared_ptr< T >(new T), kann das neue T erfolgreich sein, während die Zuweisung des Steuerblocks von shared_ptr fehlschlagen kann. In diesem Szenario wird das neu zugewiesene T durchsickern, da der shared_ptr nicht wissen kann, dass es direkt erstellt wurde, und es sicher gelöscht werden kann. oder übersehe ich etwas? Ich glaube nicht, dass die strengeren Regeln zur Funktionsparameterbewertung hier in irgendeiner Weise helfen …

981680cookie-checkUnterschied zwischen make_shared und normal shared_ptr in C++

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy