Der Unterschied besteht darin, dass std::make_sharedeine Heap-Zuweisung ausgeführt wird, während beim Aufrufen des std::shared_ptrKonstruktors zwei ausgeführt werden.
Wo finden die Heap-Zuweisungen statt?
std::shared_ptr verwaltet zwei Entitäten:
- der Steuerblock (speichert Metadaten wie Ref-Count, Typ-gelöschter Deleter usw.)
- das zu verwaltende Objekt
std::make_sharedführt eine einzelne Heap-Zuordnung durch, die den für den Steuerblock und die Daten erforderlichen Speicherplatz berücksichtigt. Im anderen Fall wird new Obj("foo")eine Heap-Zuordnung für die verwalteten Daten aufgerufen, und der std::shared_ptrKonstruktor führt eine weitere für den Steuerblock aus.
Weitere Informationen finden Sie in den Implementierungshinweisen unter cppreference .
Update I: Ausnahmesicherheit
HINWEIS (30.08.2019) : Dies ist seit C ++ 17 kein Problem, da sich die Auswertungsreihenfolge der Funktionsargumente geändert hat. Insbesondere muss jedes Argument für eine Funktion vollständig ausgeführt werden, bevor andere Argumente ausgewertet werden.
Da sich das OP anscheinend über die Ausnahmesicherheit 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 Unterausdrücken ermöglicht, ist eine mögliche Reihenfolge:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Angenommen, in Schritt 2 wird eine Ausnahme ausgelöst (z. B. hat der RhsKonstruktor eine Ausnahme wegen Speichermangel ausgelöst ). Wir verlieren dann den in Schritt 1 zugewiesenen Speicher, da nichts die Möglichkeit gehabt hat, ihn zu bereinigen. Der Kern des Problems besteht darin, dass der Rohzeiger nicht sofort an den std::shared_ptrKonstruktor übergeben wurde.
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, std::make_sharedstattdessen zu verwenden .
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Update II: Nachteil von std::make_shared
Zitiert Caseys Kommentare:
Da es nur eine Zuordnung gibt, kann der Speicher des Pointees erst freigegeben werden, wenn der Steuerblock nicht mehr verwendet wird. A weak_ptrkann den Steuerblock unbegrenzt am Leben halten.
Warum halten Instanzen von weak_ptrs den Steuerblock am Leben?
Es muss eine Möglichkeit für weak_ptrs geben, festzustellen, ob das verwaltete Objekt noch gültig ist (z. B. für lock). Dazu überprüfen sie die Anzahl der shared_ptrs, denen das verwaltete Objekt gehört, das im Steuerblock gespeichert ist. Das Ergebnis ist, dass die Steuerblöcke so lange aktiv sind, bis sowohl die shared_ptrZählung als auch die weak_ptrZählung 0 erreichen.
Zurück zu std::make_shared
Da std::make_sharedsowohl für den Steuerblock als auch für das verwaltete Objekt eine einzige Heap-Zuordnung vorgenommen wird, 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 Steuerblock als auch das verwaltete Objekt freigeben können. Dies geschieht, bis keine shared_ptrs oder weak_ptrs mehr lebendig sind.
Angenommen, wir haben stattdessen zwei Heap-Zuordnungen für den Steuerblock und das verwaltete Objekt über newund shared_ptrKonstruktor durchgeführt. Dann geben wir den Speicher für das verwaltete Objekt frei (möglicherweise früher), wenn kein shared_ptrs aktiv ist, und geben den Speicher für den Steuerblock frei (möglicherweise später), wenn kein weak_ptrs aktiv ist.