std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
Es gibt viele Google- und Stackoverflow-Posts, aber ich kann nicht verstehen, warum dies make_shared
effizienter ist als die direkte Verwendung shared_ptr
.
Kann mir jemand Schritt für Schritt erklären, wie Objekte erstellt und von beiden ausgeführt werden, damit ich verstehen kann, wie make_shared
effizient sie sind? Ich habe oben ein Beispiel als Referenz gegeben.
c++
c++11
shared-ptr
Anup Buchke
quelle
quelle
make_shared
Sie schreiben können, hatauto p1(std::make_shared<A>())
p1 den richtigen Typ.Antworten:
Der Unterschied besteht darin, dass
std::make_shared
eine Heap-Zuweisung ausgeführt wird, während beim Aufrufen desstd::shared_ptr
Konstruktors zwei ausgeführt werden.Wo finden die Heap-Zuweisungen statt?
std::shared_ptr
verwaltet zwei Entitäten: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 wirdnew Obj("foo")
eine Heap-Zuordnung für die verwalteten Daten aufgerufen, und derstd::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,
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 denstd::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.
Der bevorzugte Weg, dies zu lösen, ist natürlich,
std::make_shared
stattdessen zu verwenden .Update II: Nachteil von
std::make_shared
Zitiert Caseys Kommentare:
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ürlock
). Dazu überprüfen sie die Anzahl dershared_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 dieshared_ptr
Zählung als auch dieweak_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 keineshared_ptr
s oderweak_ptr
s mehr lebendig sind.Angenommen, wir haben stattdessen zwei Heap-Zuordnungen für den Steuerblock und das verwaltete Objekt über
new
undshared_ptr
Konstruktor durchgeführt. Dann geben wir den Speicher für das verwaltete Objekt frei (möglicherweise früher), wenn keinshared_ptr
s aktiv ist, und geben den Speicher für den Steuerblock frei (möglicherweise später), wenn keinweak_ptr
s aktiv ist.quelle
make_shared
Eckfalls zu erwähnen : Da es nur eine Zuordnung gibt, kann der Speicher des Pointees erst freigegeben werden, wenn der Steuerblock nicht mehr verwendet wird. Aweak_ptr
kann den Steuerblock unbegrenzt am Leben halten.make_shared
undmake_unique
konsequent, werden Sie nicht roh Zeiger haben besitzen eine kann jedes Vorkommen behandelnnew
als Code Geruch.shared_ptr
und keinweak_ptr
s gibt, wird durch Aufrufenreset()
dershared_ptr
Instanz der Steuerblock gelöscht. Dies ist aber unabhängig davon oder obmake_shared
verwendet wurde. Die Verwendungmake_shared
macht einen Unterschied, da sie die Lebensdauer des für das verwaltete Objekt zugewiesenen Speichers verlängern kann . Wenn dieshared_ptr
Anzahl 0 trifft, wird der Destruktor für das verwaltete Objekt aufgerufen , unabhängig davonmake_shared
, aber befreiend nur sein Gedächtnis, wenn geschehenmake_shared
wurde nicht verwendet. Hoffe das macht es klarer.Der gemeinsam genutzte Zeiger verwaltet sowohl das Objekt selbst als auch ein kleines Objekt, das den Referenzzähler und andere Verwaltungsdaten enthält.
make_shared
kann einen einzelnen Speicherblock zuweisen, um beide zu speichern; Wenn Sie einen gemeinsam genutzten Zeiger aus einem Zeiger auf ein bereits zugewiesenes Objekt erstellen, muss ein zweiter Block zum Speichern des Referenzzählers zugewiesen werden.Neben dieser Effizienz
make_shared
bedeutet die Verwendung , dass Sie sich überhaupt nicht mitnew
und rohen Zeigern befassen müssen , was eine bessere Ausnahmesicherheit bietet. Es besteht keine Möglichkeit, eine Ausnahme nach dem Zuweisen des Objekts, aber vor dem Zuweisen zum intelligenten Zeiger auszulösen.quelle
Es gibt einen anderen 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 .
quelle
new
mich für die Verwendung entschieden , sonst hätte ich es verwendetmake_shared
. Hier ist eine verwandte Frage dazu: stackoverflow.com/questions/8147027/… .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, dies ist der einzige gute Grund, es nicht zu verwenden.
quelle
Ich sehe ein Problem mit std :: make_shared, es unterstützt keine privaten / geschützten Konstruktoren
quelle
Shared_ptr
: Führt zwei Heap-Zuordnungen durchMake_shared
: Führt nur eine Heap-Zuordnung durchquelle
In Bezug auf die Effizienz und die Zeit, die für die Zuweisung aufgewendet wurde, habe ich diesen einfachen Test unten durchgeführt. Ich habe viele Instanzen auf zwei Arten (nacheinander) erstellt:
Die Sache ist, dass die Verwendung von make_shared im Vergleich zur Verwendung von new die doppelte Zeit in Anspruch nahm. Bei Verwendung von new gibt es also zwei Heap-Zuweisungen anstelle einer mit make_shared. Vielleicht ist dies ein dummer Test, aber zeigt er nicht, dass die Verwendung von make_shared mehr Zeit in Anspruch nimmt als die Verwendung von new? Natürlich spreche ich nur von der verwendeten Zeit.
quelle
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> (neues T), kann das neue T erfolgreich sein, während die Zuweisung des Steuerblocks durch shared_ptr möglicherweise fehlschlägt. In diesem Szenario leckt das neu zugewiesene T, da das shared_ptr nicht wissen kann, dass es direkt erstellt wurde, und es sicher gelöscht werden kann. oder fehlt mir etwas Ich denke nicht, dass die strengeren Regeln für die Auswertung von Funktionsparametern hier in irgendeiner Weise helfen ...
quelle