wahl des richtigen smartpointer
-
Für mich stellt sich die Frage, wozu so ein
copy_ptrgut sein soll, wenn er nicht mit "derived class" Objekten klarkommt.
D.h. die Default-Variante sollte mMn. damit klarkommen.
(Oder gibt's da wichtige Anwendungsmöglichkeiten die ich übersehe?)
Also...hustbaer schrieb:
Damit wird's natürlich gleich ein schönes Stück komplizierter. Dafür mMn. auch ein schönes Stück einfacher zu verwenden.
Mit einem default_dup_policy_trait könnte man umgehen, dass die Policy in der Praxis explizit angegeben werden muss.
...braucht man ne Default-Policy die polymorph "klonen" kann. Nur kann man dummerweise in C++ beliebige Klassen nicht einfach so klonen. Also muss man es irgendwie anders machen.
Deine Variante, also eine "clone()" Memberfunktion mit genau diesem Namen zu verlangen, ist intrusive. Finde ich also nicht so gut.Wenn dann könnte ich mir noch eher folgende "dup_policy" vorstellen, analog zu dem was
boost::intrusive_ptrmacht:template <typename T> struct dup_policy { std::unique_ptr<T> dup(T const& x) { // Keine Default-Implementierung von copy_ptr_clone(), wird ggf. über ADL gefunden return std::unique_ptr<T>(copy_ptr_clone(x)); } };Und diese sollte mMn. dann Default sein.
Alternativ könnte man ein Concept-artiges Hilfs-Klassentemplate verwenden das man für den zu klonenden Typ spezialisieren muss.Ansonsten halte ich den Overhead beim Aufruf der Clone-Funktion auch mit Copier für durchaus vertretbar. Das ist ein zusätzlicher virtual Call, damit kann man sicher leben. So nen Zeiger wird man ja wohl nicht für Integers o.ä. verwenden. Die 2. Allokation fände ich da viel schlimmer.
hustbaer schrieb:
Und den Overhead der 2. Allokation kann man genau wie bei
shared_ptrdurch einemake_shared-artige Funktion vermeiden.Was für eine 2. Allokation?
Na der Copier muss ja irgendwo gespeichert werden. Siehe
shared_ptr, der macht auch ne 2. Allokation für den Deleter wenn man nichtmake_sharedverwendet.
-
Für mich stellt sich die Frage, wozu so ein copy_ptr gut sein soll, wenn er nicht mit "derived class" Objekten klarkommt.
Wie meinst du das? Zeiger auf Basisklassen sind Zeiger auf Basisklassen. Du kannst natürlich einen Kopierkonstruktor für
copy_ptr<Derived>definieren, der einencopy_ptr<Base>nimmt und auf
dessen Inhaltdynamic_cast<Derived&>(*other.get())anwendet.
Aber du solltest nicht erwarten, dass eincopy_ptr<Base>einen anderencopy_ptr<Base>so kopiert, dass die tatsächlichen most-derived-objects kopiert werden.Deine Variante, also eine "clone()" Memberfunktion mit genau diesem Namen zu verlangen, ist intrusive. Finde ich also nicht so gut.
Du kannst natürlich mit Type-Erasure etwas schönes basteln. Also eine virtuelle clone()-Funktion eines Wrappers. Das ganze stelle ich mir so *ganz grob* vor
template<typename T> struct clone_ptr { T* ptr; struct WrapperBase { virtual clone_ptr<T> clone(T*) = 0; }* mWrapper; template<typename Derived> struct Wrapper : WrapperBase { virtual clone_ptr<T> clone(T* ptr) { return new Derived(dynamic_cast<Derived&>(*ptr)); } }; template<typename T2, typename = typename std::enable_if<std::is_base_of<T, T2>::value>::type> clone_ptr( T2* c ): ptr{c}, mWrapper{ new Wrapper<T2> ) {} };Bin aber schon sehr müde und sehe mir das morgen Nachmittag genauer an.
-
Ein klassischer "ich weiss was!!!11elf" Beitrag von unserem liebsten "ich weiss was!!!11elf" User. *sigh*
Arcoth schrieb:
Für mich stellt sich die Frage, wozu so ein copy_ptr gut sein soll, wenn er nicht mit "derived class" Objekten klarkommt.
Wie meinst du das? Zeiger auf Basisklassen sind Zeiger auf Basisklassen.
(*snip-nonsense*)Ach watt!
Wenn ich einen
copy_ptr<Base>habe, wo einDeriveddrinnen steckt, und ich kopiere mir daraus einen 2.copy_ptr<Base>, dann soll da wieder einDeriveddrin stecken.Du kannst natürlich mit Type-Erasure etwas schönes basteln.
Wenn du den Thread aufmerksam gelesen hättest, wäre dir vielleicht aufgefallen, dass das mein 1. Vorschlag war.
Also eine virtuelle clone()-Funktion eines Wrappers. Das ganze stelle ich mir so *ganz grob* vor
(*snip*)*sigh*
Ja, ich denke die meisten hier wissen wie man sowas angeht.
Und eine nichtmal 1/2-fertige Lösung ist wohl kaum interessant.Davon abgesehen kann eine Klasse die (ausser einer Memberfunktion) nichts enthält wohl auch nichts "Wrappen".
=> Cloner, Copier, aber sicher nicht Wrapper.
Und derdynamic_castist auch problematisch. Erstens wollen wir für den ja nicht bezahlen, und zweitens wollen wir vonTauch nicht verlangen dass es polymorph sein muss.
-
Wozu zerbrecht ihr euch den Kopf? Ich habe oben einen Link zu einer Bibliothek gepostet, die eine fertige Implementierung für einen Smart-Pointer mit Deep-Copy-Semantik anbietet, sogar über polymorphe Objekte ohne clone()-Funktion hinweg.
Die Verwendung ist äusserst simpel:
aurora::CopiedPtr<Base> a(new Derived); aurora::CopiedPtr<Base> b = a; // erstellt neues Derived-ObjektAusserdem kann man weitere Cloner- und Deleter-Strategien angeben.
-
Aurorer schrieb:
Wozu zerbrecht ihr euch den Kopf? Ich habe oben einen Link zu einer Bibliothek gepostet, die eine fertige Implementierung für einen Smart-Pointer mit Deep-Copy-Semantik anbietet, sogar über polymorphe Objekte ohne clone()-Funktion hinweg.
Die Lib erfordert sehr wohl eine vituelle clone-Funktion.
-
nwp3 schrieb:
Die Lib erfordert sehr wohl eine vituelle clone-Funktion.
Tut sie nicht, du guckst nicht richtig.
@Aurorer
Weil es für manche vielleicht auch interessant ist zu wissen wie bestimmte Dinge funktionieren.
Davon abgesehen ist die Implementierung die du verlinkt hast durchaus verbesserungswürdig.
-
Aurorer schrieb:
Deep-Copy-Semantik anbietet, sogar über polymorphe Objekte ohne clone()-Funktion hinweg.
Ich bin mir gar nicht so sicher, ob das gut ist.
-
@volkard
Was würdest du mit einem kopierenden Zeiger anstellen der das nicht macht? Also der beim Kopieren ein Objekt der Basisklasse erstellt (slicing).
In so einem Fall würde ich gleich den gewünschten Typ direkt verwenden. Oder ggf.boost::optional<T>, wenn ich lazy initialization bzw. allgemein einen "empty" Zustand brauche.Oder meinst du so ein Zeiger grundsätzlich ist gut, aber er sollte nicht "einfach so" funktionieren?
-
hustbaer schrieb:
nwp3 schrieb:
Die Lib erfordert sehr wohl eine vituelle clone-Funktion.
Tut sie nicht, du guckst nicht richtig.
Ok, ich habe nicht bis zu ende gelesen. Ich mag es trotzdem nicht. Man muss im gesamten Code aufpassen, dass man immer den dynamischen Typ übergibt. Nicht wirklich ein Vorteil gegenüber einer vergessbaren clone-Funktion, wo man nur in den Klassendeklarationen nach einem fehlenden clone suchen muss.
Man warte auf C++2x, dann gibts virtuelle Copy-Konstruktoren und dann funktioniert das out of the box.(Ich hasse es englisch und deutsch zu mischen)
-
nwp3 schrieb:
Man muss im gesamten Code aufpassen, dass man immer den dynamischen Typ übergibt.
Auch nur dann, wenn man zeitweise mit rohen Zeigern arbeitet, was bei Smartpointern i.d.R. vermeidbar ist. Typischerweise stammen die aus irgendwelchen Factories oder eben einem einfachen make_clone_ptr.
-
Bzgl Kopieren/Clonen hatte ich das mal ungefähr so gelöst:
#define AUTO_RETURN(...) ->decltype(__VA_ARGS__) {return __VA_ARGS__;} namespace clone_details { struct fallback_on_copy {}; struct prefer_clone : fallback_on_copy {}; template<class T> auto doit(T const* ptr, fallback_on_copy) AUTO_RETURN( new T(*ptr) ) template<class T> auto doit(T const* ptr, prefer_clone) AUTO_RETURN( ptr->clone() ) } // clone_details template<class T> struct default_clone_destroy { auto clone(T const* ptr) AUTO_RETURN(clone_details::doit(ptr,prefer_clone())) void destroy(T* ptr) { delete ptr; } };(so runtergetippt und nicht getestet)
...so dass man in den meisten Fällen einfach
default_clone_destroyzum Klonen und Löschen benutzen kann
-
Mir faellt noch eine Variante ein, grundlegend saehe das so aus:
#include <type_traits> #include <utility> template< typename T > class clone_ptr_polymorph { void* mDerivedPtr; T* mBasicPtr; std::pair<T*, void*> (* mCloner)(void*); template< typename Derived > static std::pair<T*, void*> clone(void* ptr) { auto obj = new Derived{ *reinterpret_cast<Derived*>(ptr) }; return { obj, obj }; } public: template< typename Derived, typename=typename std::enable_if<std::is_base_of<T, Derived>::value>::type > clone_ptr_polymorph( Derived* ptr ) : mDerivedPtr{ptr}, mBasicPtr{ptr}, mCloner{ &clone<Derived> } {} clone_ptr_polymorph( clone_ptr_polymorph const& p ) : mCloner{p.mCloner} { auto pair = mCloner(p.mDerivedPtr); mBasicPtr = pair.first; mDerivedPtr = pair.second; } ~clone_ptr_polymorph() { delete mBasicPtr; } clone_ptr_polymorph( clone_ptr_polymorph&& ) = default; clone_ptr_polymorph& operator=( clone_ptr_polymorph&& ) = default; }; template<typename Base, typename Der, typename... Args> clone_ptr_polymorph<Base> make_clone_ptr( Args&&... args ) { return { new Der(std::forward<Args>(args)...) }; } #include <iostream> struct Base { virtual ~Base() { std::cout << "Base-Destructor\n"; } Base() { std::cout << "Base-Def-Ctor\n"; } Base(Base const&) { std::cout << "Base-Copy-Ctor\n"; } Base(Base&&) { std::cout << "Base-Move-Ctor\n"; } }; struct Derived : Base { ~Derived() { std::cout << "Derived-Destructor\n"; } Derived() { std::cout << "Derived-Def-Ctor\n"; } Derived(Derived const&) { std::cout << "Derived-Copy-Ctor\n"; } Derived(Derived&&) { std::cout << "Derived-Move-Ctor\n"; } }; int main() { auto const& p = make_clone_ptr<Base, Derived>(); auto second = p; }http://ideone.com/MQ2xTv
(Bin gerade nicht konzentriert, da in der Schule, aber so sollte wenigstens hustbaer nicht einen halben Fetzen darin sehen)Edit: Ich glaube, der Ideone Output ist merkwuerdig... habe ich da boese rumgepfuscht? wieso taucht da
Base-Def-Ctor
Derived-Copy-Ctor
auf?
-
@hustbaer: Kann gut sein, was würdest du konkret verbessern?
@nwp3: Man muss den dynamischen Typen nur bei der Initialisierung angeben. In vielen Fällen kennt man ihn dann, weil man das Objekt gerade konstruiert. Und ansonsten nimmt man eben die clone-Strategie.
-
Ach nein, der (die Ausgabe) ist richtig. Ich habe den copy-ctor ja selbst definiert. :schaem:
-
hustbaer schrieb:
@volkard
Was würdest du mit einem kopierenden Zeiger anstellen der das nicht macht? Also der beim Kopieren ein Objekt der Basisklasse erstellt (slicing).
In so einem Fall würde ich gleich den gewünschten Typ direkt verwenden. Oder ggf.boost::optional<T>, wenn ich lazy initialization bzw. allgemein einen "empty" Zustand brauche.Oder meinst du so ein Zeiger grundsätzlich ist gut, aber er sollte nicht "einfach so" funktionieren?
Ich meinte, daß es evtl gar nicht gut ist, den Benutzer nicht zu zwingen, zu diesem Zwecke clone() selber anzubieten und in die Klassen zu schreiben, hübsch bei den großen 3/5/6.
-
Der richtige smart pointer hat einen Default-Konstruktor, der automatisch eine Instanz des Programmes das man haben möchte erzeugt. Alles andere ist doch nur Augenwischerei.
-
smarter typ schrieb:
Der richtige smart pointer hat einen Default-Konstruktor, der automatisch eine Instanz des Programmes das man haben möchte erzeugt. Alles andere ist doch nur Augenwischerei.
Aber solch ein Smart-Pointer wäre nur Penisverlängerung... Wer braucht sowas?!
-
Skym0sh0 schrieb:
smarter typ schrieb:
Der richtige smart pointer hat einen Default-Konstruktor, der automatisch eine Instanz des Programmes das man haben möchte erzeugt. Alles andere ist doch nur Augenwischerei.
Aber solch ein Smart-Pointer wäre nur Penisverlängerung... Wer braucht sowas?!
Faule Programmierer mit Freundin.
-
Seit wann haben Programmierer/Informatiker Freundinnen? oO
Und faul sind wir doch alle, per Definition. Und ungeduldig und überheblich...
-
Skym0sh0 schrieb:
Seit wann haben Programmierer/Informatiker Freundinnen? oO
Und faul sind wir doch alle, per Definition. Und ungeduldig und überheblich...
ist doch einfach...
this->girlfriend = &nadine; nadine.owner = this;sooo, bitteschön!