Missing clone_ptr / value_ptr
-
Was aber nicht immer möglich ist. Hier ging es ja ganze Zeit um Polymorpie. Aber auch ohne kann es Sinn machen. Z.B. bei Datenstrukturen wie Linked Lists oder Bäumen. Wenn man die mit so einem
value_ptr
aufbauen würde braucht man sich nicht mehr um Copy Constructor kümmern.
-
sebi707 schrieb:
Aber auch ohne kann es Sinn machen. Z.B. bei Datenstrukturen wie Linked Lists oder Bäumen. Wenn man die mit so einem
value_ptr
aufbauen würde braucht man sich nicht mehr um Copy Constructor kümmern.Sehe nicht, wofür man dort einen solchen "
value_ptr
" brauchen würde. Was genau macht der, was einunique_ptr
dort nicht machen nicht kann?
-
@dot
Denvalue_ptr
bzw.clone_ptr
brauchst du wenn du Value-Semantik mit Polymorphie kombinieren willst.
-
Ist mir schon klar, nur angeblich soll der jetzt auch ohne Polymorphie was bringen und das versteh ich grad nicht!?
-
Das hat er doch gerade geschrieben, du musst den Copy-Ctor nicht implementieren.
StattFoo::Foo(Foo const& other) { if (other.m_left) m_left.reset(other.m_left->clone()); if (other.m_right) m_right.reset(other.m_right->clone()); }
schreibst du einfach ... nix.
Nix schreiben ist einfacher. Und weniger fehleranfällig.
-
Naja großartig, dann hast du deinen Copy C'tor halt
clone()
genannt, implementieren musst du es trozdem. Ich ging davon aus, dass es selbstverständlich ist, dass man, wenn man schon meint, so einclone()
Teil haben zu müssen, den Copy C'tor private oder protected implementiert (oder defaulted) und dannauto clone() const { return std::make_unique<Blub>(*this); }
macht, weil wieso würde man irgendwas anderes machen...
-
Nein du brauchst auch keine clone Funktion. Alle Membervariablen werden durch den Aufruf ihres Copy Constructors kopiert und der
value_ptr
implementiert diesen eben so, dass nicht die Adresse kopiert wird sondern tatsächlich die Daten. Wobei wir ja schon diskutiert haben das dieser Kopiervorgang für polymorphe Objekte nicht so einfach ist. Ohne Polymorpie ist das allerdings trivial.
-
Dafür brauch ich aber keinen
value_ptr
weil der Copy C'tor macht das doch schon!?
-
Aber falsch. Wenn du Raw Pointer benutzt kopiert er nur die Adressen und wenn du
unique_ptr
benutzt gibts gar keinen Default Copy Constructor weilunique_ptr
Move only ist.
-
nach dem ansehen von 'inheritance is the base class of evil' bin ich der Meinung das ich bis dahin immer viel zu viel polymorphe Objekte gebaut habe und deswegen alles so kompliziert wurde. Sean Parent legt da einfach seine value Objekte von unterschiedlichen Typen in den Container, sehr fesch die Methode!
Ansonst, mit nur einem Pointer auf die Base Klasse wird ohne weitere Info auch ein value_ptr Probleme haben irgendetwas brachbar zu kopieren, von selber geht da also nix.
-
sebi707 schrieb:
Aber falsch. Wenn du Raw Pointer benutzt kopiert er nur die Adressen und wenn du
unique_ptr
benutzt gibts gar keinen Default Copy Constructor weilunique_ptr
Move only ist.Die Frage ist, wieso willst du "Pointer kopieren"? Wenn dein Typ Wertsemantik hat und du besitzende Zeiger drauf hast, hast du sehr wahrscheinlich was falsch gemacht...
-
dot schrieb:
Naja großartig, dann hast du deinen Copy C'tor halt
clone()
genannt, implementieren musst du es trozdem. Ich ging davon aus, dass es selbstverständlich ist, dass man, wenn man schon meint, so einclone()
Teil haben zu müssen, den Copy C'tor private oder protected implementiert (oder defaulted) und dannauto clone() const { return std::make_unique<Blub>(*this); }
macht, weil wieso würde man irgendwas anderes machen...
Das wurde doch schon alles diskutiert: es sollte möglich sein dass der
value_ptr
den normalen Copy-Ctor verwendet.dot schrieb:
Dafür brauch ich aber keinen
value_ptr
weil der Copy C'tor macht das doch schon!?Wenn du mir zeigst wie du nen Baum oder ne verkettete Liste ohne Zeiger aufbaust...
-
hustbaer schrieb:
dot schrieb:
Dafür brauch ich aber keinen
value_ptr
weil der Copy C'tor macht das doch schon!?Wenn du mir zeigst wie du nen Baum oder ne verkettete Liste ohne Zeiger aufbaust...
Die Frage ist, wo ich dabei die Ziele irgendwelcher Pointer kopieren muss...
-
@dot
Meine Geduld wird gerade sehr strapaziert.Wie kopierste deinen Baum denn?
Wie meinste kopiert sichstd::set
?
Indem es Nodes kopiert, die Zeiger auf weitere Nodes haben, die auch wieder kopiert werden müssen usw.
-
Wenn ich einen Typen mit Wertsemantik hab, steck ich die Objekte direkt in den Container, weil wieso würde ich eine sinnlose Indirektion haben wollen!? Wenn ich Typen mit Referenzsemantik hab, mach ich mir lieber eine Funktion, die mir per
make_unique(**it)
eine Deep Copy anfertigt (denn das ist die einzige Stelle, an der ich das brauche), bevor ich solch fragwürdige Konstrukte wie einenvalue_ptr
in freie Wildbahn entlasse...
-
@dot
Vergiss vielleicht einfach mal den Begriff Zeiger.
Statt dessen denk anboost::optional
.
Wenn wir polymorphie mal aussen vor lassen, dann ist das Ding worüber wir reden einboost::optional
welches rekursive Datenstrukturen erlaubt.
-
Nun, dann kann man ja einfach
boost::optional
verwenden; wobei ich persönlich das niemals tun würde, weil ich nie wieder ruhig schlafen könnte...
-
Ich habe mal aus Spaß an der Freude gerade nochmal eine
clone_ptr
-Implementierung runtergetippt. vielleicht nützt das ja irgendeinem hier.Das Ding verwendet zwei Templateparameter, wobei der zweite für das Funktionsobjekt da ist, was das Klonen durchführt. Ein
default_clone
Objekt kommt sowohl mit trivialen Typen als auch polymorphen Klassen klar, die eine entsprechendeclone()
-Methode anbieten. Hier ist drauf zu achten, dass der Rückgabetyp derclone()
-Methode zum statischen Typ der Klasse passen sollte. C++ erlaubt hier ja in einer abgeleiteten Klasse stattBase*
auchDerived*
zurückzugeben. Wenn also einclone_ptr<Derived>
kopiert werden soll, braucht man für den Klon ja wieder einDerived*
und keinBase*
.Andere Implementierungsdetails:
- Die Typen
fallback_on_copy
undprefer_cloning
sind dazu dar, bei der Überladungsauflösung die Überladung mit Argumenttypprefer_cloning
zu bevorzugen (siehe Tag Dispatching). - Bei den Funktionstemplates
default_clone_impl
"findet SFINAE statt" wegen->decltype
statt. Deswegen benutze ich da auchidentity
. Ich will ja, dass die Rückgabe vonclone
implizit in einT*
wandelbar ist (wenn es das noch nicht ist). Wenn die Ausdrücke in->decltype(...)
"nicht klappen", wird das Template einfach ignoriert. - Ein
clone_ptr<Derived>
zu einemclone_ptr<Base>
zu konvertieren, wobei derclone_ptr<Base>
das Objekt dann immer noch korrekt löschen kann, geht ja nur, wenn der Destruktor vonBase
virtuell ist. Das habe ich per SFINAE als Bedingung für den Konvertierungskonstruktor gemacht, damit man sich nicht in den Fuß schießen kann, wenn man vergisst, den Destruktor virtuell zu machen. - Als Datenelement verwende ich ein
std::tuple<>
, statt Zeiger und Cloner separat zu halten. Das erlaubt die "empty base class optimization" von tuple. Diesen Trick habe ich von typischenunique_ptr
-Implementierungen abgeguckt. Er führt dazu, dass "leere Clone-Typen" nicht zur Größe desclone_ptr
s beitragen, so dass derclone_ptr
genauso groß wie ein roher Zeiger ist.
#include <iostream> #include <type_traits> #include <tuple> #include <utility> #include <algorithm> struct fallback_on_copy {}; struct prefer_cloning : fallback_on_copy {}; // more specialized! template<class T> auto identity(T x) -> T { return x; } template<class T> auto default_clone_impl(T const* ptr, fallback_on_copy) -> decltype(new T(*ptr)) { return new T(*ptr); } template<class T> // this is preferred if both overloads are viable auto default_clone_impl(T const* ptr, prefer_cloning) -> decltype(identity<T*>(ptr->clone())) { return ptr->clone(); } struct default_clone { template<class T> auto operator()(T const* ptr) const -> decltype(default_clone_impl(ptr, prefer_cloning{})) { return default_clone_impl(ptr, prefer_cloning{}); } }; template<class T, class Clone = default_clone> struct clone_ptr { std::tuple<T*,Clone> members_; T* & ptr_() { return std::get<0>(members_); } T* const& ptr_() const { return std::get<0>(members_); } Clone & clone_() { return std::get<1>(members_); } Clone const& clone_() const { return std::get<1>(members_); } template<class, class> friend class clone_ptr; public: explicit clone_ptr(T* ptr = nullptr, Clone c = Clone{}) : members_(ptr, std::move(c)) {} ~clone_ptr() { delete ptr_(); } clone_ptr(clone_ptr const& lvalue) : members_(lvalue.clone_()(lvalue.ptr_()), lvalue.clone_()) {} clone_ptr(clone_ptr && rvalue) : members_(rvalue.ptr_(), std::move(rvalue.clone_())) { rvalue.ptr_() = nullptr; } clone_ptr& operator=(clone_ptr temp) { std::swap(this->ptr_(), temp.ptr_()); std::swap(this->clone_(), temp.clone_()); } template<class U, class = typename std::enable_if< !std::is_same<T, U>::value && std::is_convertible<U*, T*>::value && std::has_virtual_destructor<T>::value >::type> clone_ptr(clone_ptr<U> temp) : members_(temp.ptr_(), std::move(temp.clone_())) { temp.ptr_() = nullptr; } T & operator*() { return *ptr_(); } T const& operator*() const { return *ptr_(); } T * operator->() { return ptr_(); } T const* operator->() const { return ptr_(); } }; template<class T, class...Args> auto make_clonable(Args&&...args) -> clone_ptr<T> { return clone_ptr<T> { new T(std::forward<Args>(args)...) }; } struct Base { virtual ~Base() {} virtual Base* clone() const = 0; }; struct Derived : Base { Derived() { std::cout << "Derived created\n"; } Derived(Derived const&) { std::cout << "Derived copied\n"; } ~Derived() { std::cout << "Derived destroyed\n"; } virtual Derived* clone() const override { return new Derived(*this); } }; int main() { auto c1 = make_clonable<int>(23); auto c2 = c1; // cloning clone_ptr<Derived> d = make_clonable<Derived>(); clone_ptr<Base> p = d; // cloning + implicit conversion clone_ptr<Base> q = p; // cloning (via virtual dispatch) return 0; }
- Die Typen
-
Vielleicht ein kleiner Tipp: Dein
Clone
Typ wird in der Regel leer sein, statt diesemstd::tuple
Konstrukt (wieso auch immer dasstd::tuple
) besser private vonClone
ableiten und sich auf Empty-Base-Class-Optimization verlassen, sonst wird deinclone_ptr
zwangsweise immer größer sein als ein einfacher Pointer und damit unnötig ineffizient beim Herumreichen...
-
dot schrieb:
Nun, dann kann man ja einfach
boost::optional
verwenden;Nein, kannst du nicht weil das originale
boost::optional
eben keine rekursiven Datenstrukturen unterstützt.dot schrieb:
wobei ich persönlich das niemals tun würde, weil ich nie wieder ruhig schlafen könnte...
*facepalm*
OK.