Der Unterschied besteht darin, dass std::make_shared
eine Heap-Zuweisung ausgeführt wird, während beim Aufrufen des std::shared_ptr
Konstruktors 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_shared
fü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_ptr
Konstruktor 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 Rhs
Konstruktor 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_ptr
Konstruktor ü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_shared
stattdessen 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_ptr
kann den Steuerblock unbegrenzt am Leben halten.
Warum halten Instanzen von weak_ptr
s den Steuerblock am Leben?
Es muss eine Möglichkeit für weak_ptr
s geben, festzustellen, ob das verwaltete Objekt noch gültig ist (z. B. für lock
). Dazu überprüfen sie die Anzahl der shared_ptr
s, 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_ptr
Zählung als auch die weak_ptr
Zählung 0 erreichen.
Zurück zu std::make_shared
Da std::make_shared
sowohl 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_ptr
s oder weak_ptr
s mehr lebendig sind.
Angenommen, wir haben stattdessen zwei Heap-Zuordnungen für den Steuerblock und das verwaltete Objekt über new
und shared_ptr
Konstruktor durchgeführt. Dann geben wir den Speicher für das verwaltete Objekt frei (möglicherweise früher), wenn kein shared_ptr
s aktiv ist, und geben den Speicher für den Steuerblock frei (möglicherweise später), wenn kein weak_ptr
s aktiv ist.