Deep Copy eines std::shared_ptr
-
Hallo,
wenn ich eine tiefe Kopie eines shared_ptr erstellen will, die ich in einen Container einfüge, sind dann die folgenden Varianten gleichwertig oder gibt es eine bevorzugte?
std::vector<std::shared_ptr<MyClass>> container; // sei sharedPtr ein vorhandener std::shared_ptr<MyClass> container.push_back(std::make_shared<MyClass>(*sharedPtr)); container.emplace_back(new MyClass(*sharedPtr)); container.emplace_back(std::make_shared<MyClass>(*sharedPtr));
Anders gefragt: wenn ich aus einer vorhandenen Instanz einer Klasse einen shared_ptr generieren will, habe ich dann überhaupt irgendeinen Vorteil von der Nutzung von make_shared?
-
Was hat Vor-/Nachteil von make_shared damit zu tun, mit welchen Werten du das Objekt instantiierst?
-
die dritte variante würde ich nie verwenden, da emplace_back ja nur dann seinen spezifischen vorteil bietet (vermeiden von unnötigen copy oder move-konstruktoren), wenn du variante 2 wählst. wählst du die dritte variante, verwendest du emplace_back nicht "falsch", aber "schräg", weil du selbst alle möglichen vorteile (in dem fall: ein move) aushebelst.
eine weitere überlegung:
was du nicht berücksichtigst, ist, dass du eine tiefe kopie ja wohl hauptsächlich dann haben willst, wenn du nicht weißt, welchen konkreten typ du vor dir hast. in diesem fall bist du mit deinen varianten sowieso aufgeschmissen und würdest so etwas in der art tun:container.emplace_back(sharedPtr->clone());
- und das bietet dir in aller regel genau die semantik mit dem (potentiell) möglichen performance-vorteil (vermeiden eines moves).
vielleicht ist dein beispiel eines der ganz, ganz wenigen beispiele, in der ein nacktes new einen möglichen (kleinen) vorteil bietet, generell würde ich aber *immer* make_shared bevorzugen, also variante eins, trotz des möglichen "nachteils" in der performance (ist aber nur ein zusätzliches move).
der vorteil von make_shared ist, dass du kein nacktes new in deinem code hast.
-
@DeepSharedCopy
Im Prinzip das was dove schreibt.container.emplace_back(new MyClass(*sharedPtr));
Könnte leaken.emplace mit copy-ctor ist sinnlos, hat dove ja auch schon geschrieben.
Also werwende einfach
push_back
. Mit ner RValue, so wie in deinem Beispiel, dann moved sich das automatisch.
-
dove schrieb:
wählst du die dritte variante, verwendest du emplace_back nicht "falsch", aber "schräg", weil du selbst alle möglichen vorteile (in dem fall: ein move) aushebelst.
Wo wird ein move ausgehebelt?
-
in der dritten variante wird der move-constructor von shared_ptr aufgerufen, was in der zweiten variante nicht nötig ist. emplace_back gibt es gerade deshalb, um "im platz" zu konstruieren (placement new).
wenn ich jetzt nichts falsch mache (kein compiler), kannst du das so austesten:
template <class T> class fake_ptr { T* obj; public: fake_ptr(fake_ptr const& o) : obj{nullptr} { cout << "copy ctor\n"; } fake_ptr(fake_ptr&& o) : obj{o.obj} { o.obj = nullptr; cout << "move ctor\n"; } fake_ptr(T* t) : obj(t) { cout << "init ctor\n"; } ~fake_ptr() { delete obj; } fake_ptr& operator=(fake_ptr const&) { cout << "copy assign\n"; return *this; } fake_ptr& operator=(fake_ptr&& o) { if (this == std::addressof(o)) return *this; obj = o.obj; o.obj = nullptr; cout << "move assign\n"; return *this; } T& operator* () { return *obj; } T const& operator* () const { retunr *obj; } T* operator->() { return obj; } T const* operator->() const { return obj; } }; template <class T, class... Args> fake_ptr<T> make_fake(Args&& ...args) { return fake_ptr<T>(new T(std::forward<Args>(args)...))); } std::vector<std::fake_ptr<int>> container; container.reserve(10); //nicht, dass irgendwas aus versehen kopiert wird. auto fake = make_fake<int>(42); // sei sharedPtr ein vorhandener std::shared_ptr<MyClass> cout << "test push_back:\n"; container.push_back(std::make_fake<int>(*fake)); cout << "test emplace_back 1:\n"; container.emplace_back(new int{23}); cout << "test emplace_back 2:\n"; container.emplace_back(std::make_fake<int>(*fake))
während bei push_back und emplace_back ein init ctor und ein move ctor aufgerufen werden muss, kann emplace_back in der "normalen" variante dank perfect forwarding das objekt (in dem fall den int) direkt dort erzeugen, wo er auch bleibt.
-
container.emplace_back(new MyClass(*sharedPtr));
Ist böse, da nicht exceptionsicher.
Ausserdem ineffizient, weil die Erzeugung eines shared_ptr von einem normalen Zeiger eine zusätzliche Speicherallokation erfordert. Wer diese Variante empfiehlt, hat nicht verstanden, wieso make_shared eingeführt wurde.
Zwischen push_back und emplace_back eines durch make_shared erzeugten Zeigers besteht letztlich kein Unterschied:
push_back benutzt sowieso immer Copy- oder Movekonstruktor, emplace_back benutzt einen passenden Konstruktor, was hier ebenso auf den Movekonstruktor hinausläuft, eben weil das Argument zufällig den gleichen Typ hat wie das Containerelement, das erzeugt werden soll.
-
Vielen Dank für die zahlreichen Antworten!
Den Unterschied zwischen emplace_back und push_back hatte ich offensichtlich verstanden. Eure Antworten bestätigen genau das, was ich erwartet hatte.
Die Vorteile von make_shared muss ich mir allerdings nochmal vergegenwärtigen. Falls jemand einen Tipp hat, wo es dazu etwas nachzulesen gibt, dann gerne her damit.
-
...habe selbst Literatur gefunden: Scott Meyers beschreibt es sehr gut, denke ich.
-
camper schrieb:
container.emplace_back(new MyClass(*sharedPtr));
Ist böse, da nicht exceptionsicher.
Und welche (nicht kritische) Exception soll geworfen werden, nachdem
new
ausgewertet aber bevor das interneshared_ptr
-Objekt konstruiert wurde?
-
Arcoth schrieb:
camper schrieb:
container.emplace_back(new MyClass(*sharedPtr));
Ist böse, da nicht exceptionsicher.
Und welche (nicht kritische) Exception soll geworfen werden, nachdem
new
ausgewertet aber bevor das interneshared_ptr
-Objekt konstruiert wurde?bad_alloc wegen Reallokation des Containers.
-
camper schrieb:
bad_alloc wegen Reallokation des Containers.
Weswegen habe ich wohl nicht kritisch geschrieben? Ich denke nicht, das bei einem
bad_alloc
(es sei denn, man hat irgendetwas eigenes gebastelt) noch viel Aufhebens wegen eines Lecks gemacht wird?
-
Arcoth schrieb:
camper schrieb:
bad_alloc wegen Reallokation des Containers.
Weswegen habe ich wohl nicht kritisch geschrieben? Ich denke nicht, das bei einem
bad_alloc
(es sei denn, man hat irgendetwas eigenes gebastelt) noch viel Aufhebens wegen eines Lecks gemacht wird?Und seit wann unterscheidet Exceptionsicherheit zwischen kritischen und nicht-kritischen Exceptions? Das ist überhaupt eine sinnlose Unterscheidung: Exceptions werden geworfen, weil ein bestimmter Zustand nicht lokal behandelt werden kann, das impliziert, dass eine Unterscheidung zwischen kritisch und nicht-kritisch ebenso lokal nicht getroffen werden kann. Was soll eigentlich argumentiert werden? Dass Exceptionsicherheit nur nach Lust und Laune erfolgen sollte?
Arcoth schrieb:
Weswegen habe ich wohl nicht kritisch geschrieben?
Ja, warum weigentlich? Das lenkt nur ab.
-
Ich bin hier mit camper einer Meinung.
Sich dauernd zu überlegen "kann hier überhaupt ein Fehler passieren den wir sinnvoll behandeln können" führt bloss zu Kopfschmerzen und ... Fehlern.
In Ausnahmefällen mag das angebracht sein, aber i.A. ist es mMn. wesentlich sinnvoller Dinge per Default "richtig" zu machen. Statt sich immer wieder zu überlegen was man an der jeweiligen Stelle gerade alles falsch machen kann ohne damit ein Problem zu verursachen.
-
camper schrieb:
Arcoth schrieb:
camper schrieb:
container.emplace_back(new MyClass(*sharedPtr));
Ist böse, da nicht exceptionsicher.
Und welche (nicht kritische) Exception soll geworfen werden, nachdem
new
ausgewertet aber bevor das interneshared_ptr
-Objekt konstruiert wurde?bad_alloc wegen Reallokation des Containers.
ist hier nicht gerade der witz das emplace_back den neuen platz im container zu ERST allokiert und dann das new darauf anwendet?
Weil das ist doch der konkrete zweck von emplace_back.. man kopiert oder moved nicht unnötig da der container durch variadische template magie den käse direkt mit emplacement new auf das containerelement anwendet.. Also muss es ja vorhanden sein
-
asdfasdsdasd schrieb:
ist hier nicht gerade der witz das emplace_back den neuen platz im container zu ERST allokiert und dann das new darauf anwendet?
Richtig, nur ist das Containerelement ein shared_ptr und nicht das worauf dieser zeigen soll. Das worauf er zeigen soll existiert bereits beim Aufruf der Funktion und wird nur über einen rohen Zeiger referenziert. Und im Gegensatz zu shared_ptr<T> hat vector<shared_ptr<T>> nat. keine spezielle Regel, die besagt, dass im Fall von geworfenen Exceptions vorher noch delete auf irgendwelche Zeigerargumente angewendet wird.
-
Exceptions werden geworfen, weil ein bestimmter Zustand nicht lokal behandelt werden kann, das impliziert, dass eine Unterscheidung zwischen kritisch und nicht-kritisch ebenso lokal nicht getroffen werden kann.
Tut es das? Einer kritische Exception muss nicht die unmittelbare Terminierung des Programms folgen. Ich kann, auch von außen, auf eine kritische Exception gewissermaßen reagieren. Genauso wie nicht-kritische Exceptions keine lokale Behandlung implizieren.
Was soll eigentlich argumentiert werden? Dass Exceptionsicherheit nur nach Lust und Laune erfolgen sollte?
Wie kommst du darauf, dass irgendetwas argumentiert werden soll? Ich wollte lediglich wissen, warum das konkrete Statement "böse" genannt wurde, wo AFAICS keinerlei praktische Gefahr besteht: Das Programm müsste gewiss vorzeitig beendet werden, da die Reallokation fehlschlug (welche selbst nicht all zu viel Speicher benötigen dürfte).
Das ändert aber nichts, und ich stimme natürlich zu, dass das Statement schlechtem Stil entspringt (und den anderen Punkten sowieso), und man sich schon gar nicht für Gewöhnlich den Kopf darüber zerbricht, ob ein rohes
new
durchgehen könnte. Ich wollte keine Diskussion über konsequentes Anwenden von exception safety lostreten, das befürworte ich wohl sehr.Sich dauernd zu überlegen "kann hier überhaupt ein Fehler passieren den wir sinnvoll behandeln können" führt bloss zu Kopfschmerzen und ... Fehlern.
My word. Ich schätze, ich sollte wohl zugeben, dass ich nur clever sein wollte.