Copy Konstruktor und Vererbung
-
~john schrieb:
kleines_eichhoernchen schrieb:
Ziel soll es sein ähnlich wie in C#/NET (bin C#-Programmierer) eine Clone methode zu implementieren
Ein kleines Beispiel wie man einfach Clone erzeugt. Der SmartPointer dient dazu, daß es kein Speicherleck gibt.
#include <boost/shared_ptr.hpp> class FooA { public: virtual ~FooA() = 0; virtual boost::shared_ptr<FooA> clone () const = 0; }; class FooB :public FooA { public: virtual boost::shared_ptr<FooA> clone () const { boost::shared_ptr<FooA> p(new FooB(*this)); return p; } };Das können wir sogar noch besser machen, indem wir Kovarianz in den Rückgabetypen bekommen, zudem ist auto_ptr hier die bessere Wahl. virtual ist ein Implementationsartefakt und gehört daher nicht ins öffentliche Interface (polymorphes Verhalten benötigt zwar an einem Punkt dynamische Bindung, das muss aber keinesfalls die unmittelbar aufgerufene Funktions sein).
#include <memory> class FooA { public: virtual ~FooA() = 0; std::auto_ptr<FooA> clone () const { return std::auto_ptr<FooA>(clone_impl()); } private: virtual FooA* clone_impl() const = 0; }; class FooB :public FooA { public: std::auto_ptr<FooB> clone () const { return std::auto_ptr<FooB>(clone_impl()); } private: FooB* clone_impl() const { return new FooB(*this); } };
-
@camper: Sehe ich das richtig, dass clone_impl nur deshalb virtual ist, damit man auch vergessen "darf", clone für einen abgeleiteten Typ zu überschreiben? Oder siehst Du vor, clone sowieso nur dort zu überschreiben, wo ein auto_ptr<Derived> gefordert wird (aus welchen Gründen auch immer)?
-
LordJaxom schrieb:
@camper: Sehe ich das richtig, dass clone_impl nur deshalb virtual ist, damit man auch vergessen "darf", clone für einen abgeleiteten Typ zu überschreiben? Oder siehst Du vor, clone sowieso nur dort zu überschreiben, wo ein auto_ptr<Derived> gefordert wird (aus welchen Gründen auch immer)?
clone wird nicht überschrieben, sondern es überdeckt die entsprechende Funktion der Basisklasse. clone_impl muss, um korrekt kopieren zu können, den dynamischen Typ des Objekts kennen, folglich ist es virtuell und muss in jeder Klasse, die vollständige Objekte instantiiert, überschrieben werden. clone dagegen kennt nur den statischen Typ des Objekts, für das es aufgerufen wurde - folglich brauchen wir eine eigene Version davon in jeder Klasse des Klassenhierarchie.
-
Ich habe mal wieder nicht weit genug gedacht - natürlich weiss ein Aufruf von clone auf einem A* nichts von der Existenz von B::clone. Damit stellt sich die Frage natürlich nicht.
Überschreiben vs. Überdeckung - Begriffsverwirrung, ich bekomme die beiden nie richtig hin

-
camper schrieb:
clone dagegen kennt nur den statischen Typ des Objekts, für das es aufgerufen wurde - folglich brauchen wir eine eigene Version davon in jeder Klasse des Klassenhierarchie.
Das ist definitiv falsch, das Überschreiben ist überflüssig und gefährlich. Man braucht es nur einmal in der Basisklasse definieren.
#include <ostream> #include <iostream> class FooA { virtual FooA* clone_impl () const = 0; virtual std::ostream& print_impl (std::ostream& out) const = 0; public: std::ostream& print (std::ostream& out) { return this->print_impl(out); } std::auto_ptr<FooA> clone () const { return std::auto_ptr<FooA>(clone_impl()); } virtual ~FooA() = 0; }; FooA::~FooA(){} std::ostream& operator<< (std::ostream& out, FooA& rhs) { rhs.print(out); return out; } class FooB :public FooA { FooA* clone_impl() const { return new FooB(*this); } std::ostream& print_impl(std::ostream& out) const { out << "FooB\n"; return out; } public: ~FooB() {} }; class FooC : public FooA { FooA* clone_impl() const { return new FooC(*this); } std::ostream& print_impl(std::ostream& out) const { out << "FooC\n"; return out; } public: ~FooC() {} }; int main () { FooB b; FooC c; FooA* p = &b; std::cout << *p; p = & c; std::cout << *p; }Ich frage mich eigentlich was die Aktion mit der Methode clone sollte. Gewinnen tust Du dadurch gar nicht, weil so oder so ein Recompile und Relinking anfällt, falls irgend was in der Hierachie geändert wird. Erst wenn man das Pimpl Idiom umsetzt bringt das etwas. Denn auf die private clone_impl darf man zwar nicht zugreifen, sie ist aber immer sichtbar. Da kann man die Funktion auch direkt benutzen und erspart es sich viel überflüssigen Code zu schreiben.
-
~john schrieb:
Das ist definitiv falsch, das Überschreiben ist überflüssig und gefährlich.
Warum? Die Methode erreicht doch nur, dass ein Aufruf von B::clone einen auto_ptr<FooB> statt eines auto_ptr<FooA> zurückgibt. Der Sinn von campers Beispiel, die Kovarianz, geht bei Deinem Beispiel ausserdem wieder verloren.
-
LordJaxom schrieb:
~john schrieb:
Das ist definitiv falsch, das Überschreiben ist überflüssig und gefährlich.
Warum?
Betrachten wir mal die unterschiedlichen Möglichkeiten.
Nennen wir mal die Basisklasse "CloneContract", vielleicht wird es dann einsichtiger.Alle Klassen, die von "CloneContract" erben, können via "clone" Kopien ihrer Exemplare erzeugen ohne das ich den konkreten Typ wissen muß. Was bringt es mir dann, wenn ich eine abgeleitete Klasse habe, die einen Zeiger auf "CloneExtendedContract" zurückgibt? Nicht viel, dann alle Benutzer des "CloneContracts" erwarten, daß er diesen Contract erfüllt und nicht einen "CloneExtendedContract". Es wird also jeder "CloneExtendedContract"-Zeiger effektiv nur als "CloneContract"-Zeiger benutzt.
Daher bringt es auch wenig, hier eine Lösung zu bauen, die auf eine virtual Methode verzichtet.
Da es in C++ nicht möglich ist Methoden über den Rückgabetyp zu unterscheiden, muß ich eine neue Methode definieren, wenn ich ein "CloneExtendedContract" brauche.
... std::smart_pointer<CloneExtendedContract> cloneExtended(); ...Ich kann ohnehin Kopien erzeugen, wenn ich den konkreten Typ kenne -> CopyConstructor.
Das Überdecken von Methoden, die sich nur durch den Rückgabetyp unterscheiden, sollte man in C++ möglichst vermeiden, da C++ eben nur über die Parametertypen Methoden unterscheiden kann, und man so Wichtiges verdeckt.
-
@~john:
ad 1: richtig, aber die neue Funktion kann ruhig gleich heissen wie die alte. Die alte ist ja immernoch erreichbar (qualifizierter Aufruf), und da sie nicht virtual ist werden Programmteile die nur mit CloneContract* arbeiten auch nie die falsche Funktion aufrufen.
ad 2: Was wenn CloneExtendedContract NICHT der konkrete Typ ist, sondern selbst nur eine Basisklasse? In dem Fall würde man das Objekt "slicen", und das wäre ja wohl garnicht gut. Deswegen macht man den Copy-Ctor von "klonbaren" Objekten in C++ auch meist private - ausgenommen Fälle wo man es aus bestimmten Gründen nicht kann (wenn man z.B. Exception-Klassen klonbar macht). Der Aufruf von CloneExtendedContract::clone dagegen funktioniert immer noch.
ad 3: in dem speziellen Fall sehe ich wirklich kein Problem. Die Funktion macht immer dasselbe, der Returnwert ist semantisch immer gleich, bloss der Typ unterscheidet sich. Kannst du für diesen konkreten Fall ein Beispiel zeigen wo es zu Problemen kommen könnte, oder gar "gefährlich" wäre?
Davon abgesehen dass ich kein grundsätzliches Problem bei der Sache finden kann würde ich es auch nicht so machen, sondern eher eine Basisklasse in der Art verwenden:
class Cloneable { public: virtual ~Cloneable(){} template <class T> std::auto_ptr<T> clone() const { std::auto_ptr<Cloneable> p(clone_impl()); T* pt = dynamic_cast<T*>(p.get()); if (!pt) throw std::bad_cast(); p.release(); return std::auto_ptr<T>(pt); } private: Cloneable(Cloneableconst&); Cloneable& operator =(Cloneableconst&); virtual Cloneable* clone_impl() const = 0; };EDIT: oops, virtual dtor hatte ich vergessen (nachgetragen)
-
hustbaer schrieb:
Davon abgesehen dass ich kein grundsätzliches Problem bei der Sache finden kann würde ich es auch nicht so machen, sondern eher eine Basisklasse in der Art verwenden:
class Cloneable { public: template <class T> std::auto_ptr<T> clone() const { std::auto_ptr<Cloneable> p(clone_impl()); T* pt = dynamic_cast<T*>(p.get()); if (!pt) throw std::bad_cast(); p.release(); return std::auto_ptr<T>(pt); } private: Cloneable(Cloneableconst&); Cloneable& operator =(Cloneableconst&); virtual Cloneable* clone_impl() const = 0; };Hm, dann musst du jedesmal den Zieltyp angeben, das kommt einem cast sehr nahe. Das würde ich besonders in einfachen Fällen für etwas seltsam halten. Wieso eigentlich nicht kopierbar?. Eine Klasse, die sich clonen kann, muss doch erst recht kopierbar sein. Damit fallen dann defaultgenerierte Kopierkonstruktoren weg. Was hältst du davon:
template<typename T> std::auto_ptr<T> clone(const T* p) { return std::auto_ptr<T>(static_cast<T*>(static_cast<const Cloneable*>(p)->clone_impl())); // der umständliche cast, um nicht in jeder Klasse einen friend deklarieren zu müssen } class Cloneable { template<typename T> friend std::auto_ptr<T> clone(const T*); public: virtual ~Cloneable() {} private: virtual Cloneable* clone_impl() const = 0; };Ich bevorzuge freie Funktionen sowieso, das ist aber nicht jedermanns Sache. Die Vererbungshierarchie zu vergrößern ist nicht meine Präferenz, wäre aber hier tolerierbar.
-
Als erstes sollte man entscheiden, ob man polymorphes Verhalten zur Laufzeit braucht oder nur während der Übersetzung. Es wurde aber explizit nach einem Verhalten wie in C# gefragt. Da ist clone Laufzeit polymorph. Template Lösungen scheiden damit aus.
hustbaer schrieb:
@~john:
ad 1: richtig, aber die neue Funktion kann ruhig gleich heissen wie die alte. Die alte ist ja immernoch erreichbar (qualifizierter Aufruf), und da sie nicht virtual ist werden Programmteile die nur mit CloneContract* arbeiten auch nie die falsche Funktion aufrufen.
Trotzdem bringt das nichts. Für ein richtige Implementierung von clone braucht man polymorphes Verhalten. Einziges Problem man muß sicherstellen, daß jede Klasse "clone" reimplementiert. Auch ein "clone_impl" schützt nicht davor, daß es zu so einem Problem kommen kann, wenn jemand clone_impl vergißt zu implementieren, das umschliessende clone hilft da nichts.
So meine Version mit der Garantie, daß es kein Slicing via clone gibt. Die Anpassung an std::auto_ptr dürfte wohl trivial sein.
#include <boost/shared_ptr.hpp> #include <stdexcept> #include <string> class Cloneable { protected: template <typename T> static boost::shared_ptr<Cloneable> cloneObject (T const*const self) { if (typeid (*self) != typeid (T)) { std::string s = "Cloneable.clone() slicing "; s += typeid(*self).name(); throw std::logic_error (s); } return boost::shared_ptr<Cloneable> p(new T(*self)); } public: virtual ~Cloneable() = 0; virtual boost::shared_ptr<Cloneable> clone () const = 0; }; Cloneable::~Cloneable() {} class FooB : public Cloneable { public: virtual boost::shared_ptr<Cloneable> clone () const { return Cloneable::cloneObject (this); } }; class FooC : public FooB { public: /* virtual boost::shared_ptr<Cloneable> clone () const { return Cloneable::cloneObject (this); } */ }; int main () { FooC c; boost::shared_ptr<Cloneable> p (c.clone()); }
-
~john schrieb:
Als erstes sollte man entscheiden, ob man polymorphes Verhalten zur Laufzeit braucht oder nur während der Übersetzung.
Das liegt allerdings weitab vom Thema. Im Prinzip wäre es natürlich wünschenswert, die Implementation von clone_impl durch den Compiler aus einem Template, das über den Typ der jeweiligen Klasse parametrisiert ist, generieren zu lassen, so eine Art Template hat C++ allerdings nicht.
~john schrieb:
Es wurde aber explizit nach einem Verhalten wie in C# gefragt.
Es wurde nach einem Verhalten ähnlich dem in C# gefragt. Ich kenne C# nicht, falls das Äquivalent deiner Lösung tatsächlich das Optimum in C# sein sollte, wäre ich etwas enttäuscht.
~john schrieb:
Da ist clone Laufzeit polymorph.
Das ist es bisher in allen Lösungen gewesen.
~john schrieb:
Template Lösungen scheiden damit aus.
Den Schluss kann ich nicht nachvollziehen. Außerdem widersprichst du dir dann selbst mit deiner eigenen Lösung.
hustbaer schrieb:
@~john:
ad 1: richtig, aber die neue Funktion kann ruhig gleich heissen wie die alte. Die alte ist ja immernoch erreichbar (qualifizierter Aufruf), und da sie nicht virtual ist werden Programmteile die nur mit CloneContract* arbeiten auch nie die falsche Funktion aufrufen.
[/quote]Die Möglichkeit des qualifizierten Aufrufes ist gar nicht notwendig, aus Sicht der Basisklasse kommt es überhaupt nicht darauf an, ob ihre Funktionen in einer abgeleiteten Klasse direkt aufrufbar sind, es reicht schon, dass die abgeleitete Klasse wie eine Basisklasse erscheinen kann (per impliziter Pointer-/lvalue-Konvertierung) um LSP zu genügen.
~john schrieb:
Trotzdem bringt das nichts. Für ein richtige Implementierung von clone braucht man polymorphes Verhalten. Einziges Problem man muß sicherstellen, daß jede Klasse "clone" reimplementiert. Auch ein "clone_impl" schützt nicht davor, daß es zu so einem Problem kommen kann, wenn jemand clone_impl vergißt zu implementieren, das umschliessende clone hilft da nichts.
Das ist ziemlich wirr. Wenn der Fehler gemacht wird, eine Funktion nicht zu überschreiben, die überschrieben werden muss, können wir diesen Fehler nicht dadurch beheben, dass wir eine die Implementation einer anderen Funktion ändern (es sei denn, diese Änderung hebt die Bedingung auf, dass die nicht überschriebene Funktion überschrieben werden muss - dann ist jene Funktion allerdings wahrscheinlich völlig überflüssig - und dass ist bei clone offensichtlich nicht der Fall). Die Lösung kann nur darin bestehen, die fehlende Implementation nachzuholen. Punkt.
Im Prinzip kann diese Problem in einem kompilierbaren Programm gar nicht auftreten, denn das bedeutet, dass clone_impl eine Überschreibung in einer nicht abstrakten Basisklasse hat - und die Ableitung von nicht-abstrakten Klassen in einer Hierarchie wird im LSP verletzen (oder trivial und daher überflüssig sein - so wie im Beispiel). Das ist ein schwerer Designfehler, der wiederum nichts mit clone selbst zu tun hat.~john schrieb:
So meine Version mit der Garantie, daß es kein Slicing via clone gibt. Die Anpassung an std::auto_ptr dürfte wohl trivial sein.
#include <boost/shared_ptr.hpp> #include <stdexcept> #include <string> class Cloneable { protected: template <typename T> static boost::shared_ptr<Cloneable> cloneObject (T const*const self) { if (typeid (*self) != typeid (T)) { std::string s = "Cloneable.clone() slicing "; s += typeid(*self).name(); throw std::logic_error (s); } return boost::shared_ptr<Cloneable> p(new T(*self)); } public: virtual ~Cloneable() = 0; virtual boost::shared_ptr<Cloneable> clone () const = 0; }; Cloneable::~Cloneable() {} class FooB : public Cloneable { public: virtual boost::shared_ptr<Cloneable> clone () const { return Cloneable::cloneObject (this); } }; class FooC : public FooB { public: /* virtual boost::shared_ptr<Cloneable> clone () const { return Cloneable::cloneObject (this); } */ }; int main () { FooC c; boost::shared_ptr<Cloneable> p (c.clone()); }auch das ist kein richtiges Clonen. Wenn ich ein A, das Teil eine B-Objektes ist, habe, möchte ich nach dem Clonen ein A, welches Teil einer B-Kopie ist, haben; hier bekomme ich etwas Seltsames, das Teil einer B-Kopie ist. All die Lösungen, die eine zusätzliche Basisklasse einführen, drängen den Klasse unnötige Restriktionen auf, zudem versagen sie schnell in komplizierter Mehrfachvererbung - zwar ist die selten, aber gerade ein sehr einfaches und nützliches Konzept wie Cloning lebt davon, sehr allgemeingültig zu sein.
Die Lösung (die einzige Restriktion ist hier die Beschränkung auf new/delete, eigentlich gehört dort ein move_ptr hin), die mir am Besten gefällt (ja ich weiß, kein echtes C#-Äquivalent für... C#-Puristen?). Ja es ist furchtbar kompliziert zu implementieren, aber in der Anwendung umso einfacher:
template<typename T> typename boost::enable_if< boost::is_polymorphic< T >, std::auto_ptr<T> >::type clone(const T* p) { // jetzt wird es ein bisschen ungemütlich, um bei Mehrfachvererbung, bei der der Upcast mehrdeutig wäre, das richtige Subobjekt wiederzufinden // wir machen uns zunutze, dass das Speicherlayout für alle Objekte des gleichen dynamischen Typs gleich ist // ein direkter cast kommt nicht in Frage, denn der kann mehrdeutig sein const char* const complete_source_object = static_cast< const char* >( dynamic_cast< const void* >( p ) ); const std::ptrdiff_t offset_of_subobject = reinterpret_cast< const char* >( p ) - complete_source_object; void* const complete_clone = p->clone_impl(); void* const subobject_clone = static_cast< char* >( complete_clone ) + offset_of_subobject; return std::auto_ptr<T>( static_cast< T* >( subobject_clone ) ); } // für nicht-polymorphe Typen kopieren wir einfach ganz normal, um einheitliche Behandlung zu ermöglichen template<typename T> typename boost::disable_if< boost:is_polymorphic< T >, std::auto_ptr<T> >::type clone(const T* p) { return std::auto_ptr<T>( new T( *p ) ); } // jede polymorphe Klasse benötigt dann eine entsprechende Funktion clone_impl, auf die clone zugreifen kann, die - wie auch immer - das vollständige Objekt kopiert und eine Zeiger darauf zurückgibt. class Foo { ... friend std::auto_ptr<Foo> clone<Foo>(const Foo* p); protected: virtual void* clone_impl() const = 0; // oder auch Foo* clone }; class Bar : public Foo { ... // jede Klasse benötigt diese Deklaration friend std::auto_ptr<Bar> clone<Bar>(const Bar* p); // in jeder nicht-abstrakten Klasse: private: virtual void* clone_impl() const { return Bar( *this ); } }; // jeder nicht-polymorphe Typ ist sofort Cloneable, sofern auf den Copy-ctor zugegriffen werden kann
-
hustbaer schrieb:
@~john:
ad 2: Was wenn CloneExtendedContract NICHT der konkrete Typ ist, sondern selbst nur eine Basisklasse?
Dann passiert gar nichts, da man von abstrakten Typen keine Objekte erzeugen kann. Man kann in C++ Cloneable pure virtual machen und CloneableExtended ebenfalls. Gefährlich wird es nur bei konkrete Klasse erbt von konkreter Klasse, dann besteht die Gefahr, daß eine der Methoden nicht redefiniert wird. Also, muß man nur darauf achten, daß es in so einem Fall die Methode einer Klasse darüber (oder war's darunter) nicht mißbraucht werden kann. Code habe ich dazu ja gepostet.
ad 3: in dem speziellen Fall sehe ich wirklich kein Problem. Die Funktion macht immer dasselbe, der Returnwert ist semantisch immer gleich, bloss der Typ unterscheidet sich. Kannst du für diesen konkreten Fall ein Beispiel zeigen wo es zu Problemen kommen könnte, oder gar "gefährlich" wäre?
Erstens vernebelt die Member Function die Tatsache, daß die eigentliche Arbeit nur in der virtuellen Methode durchgeführt wird. Es hat also gar keinen Sinn sie zu definieren. Zweitens kann dies beim Ableiten schnell zu Fehlern führen, wenn jemand die Methode wie folgt schreibt
class FooC : public FooB { ... std::auto_ptr<FooB> clone () const { return this->clone_impl(); } // clone_impl wird nicht reimplementiert ... };
-
camper schrieb:
auch das ist kein richtiges Clonen.
Du hast doch nur einen Template Verbau drumherum gemacht, damit es auch ohne Mehrfachvererbung funktioniert, und dadurch wurde es notwendig, die Methode als freistehende Funktion zu implementieren. Java & Co. benutzen den Interface Klassen Ansatz == Mehrfachvererbung, das hatte ich schon zu Anfang geschrieben.
-
camper schrieb:
Im Prinzip kann diese Problem in einem kompilierbaren Programm gar nicht auftreten, denn das bedeutet, dass clone_impl eine Überschreibung in einer nicht abstrakten Basisklasse hat - und die Ableitung von nicht-abstrakten Klassen in einer Hierarchie wird im LSP verletzen (oder trivial und daher überflüssig sein - so wie im Beispiel).
Das ist unsinnig. Man kann sehr wohl von einer konkreten Klasse erben, die schon die pure virtual Methode definiert hat, und dann kommt es möglicherweise zu Slicing. Es gibt in C++ kein Sprachkonstrukt, daß erzwingen würde, daß alle Klassen eine bestimme Methode redefinieren.
P.S. Mein Beispiel zeigt die Problematik übrigens auf.
-
@~john:
~john schrieb:
hustbaer schrieb:
@~john:
ad 2: Was wenn CloneExtendedContract NICHT der konkrete Typ ist, sondern selbst nur eine Basisklasse?
Dann passiert gar nichts, da man von abstrakten Typen keine Objekte erzeugen kann. Man kann in C++ Cloneable pure virtual machen und CloneableExtended ebenfalls. Gefährlich wird es nur bei konkrete Klasse erbt von konkreter Klasse, dann besteht die Gefahr, daß eine der Methoden nicht redefiniert wird. Also, muß man nur darauf achten, daß es in so einem Fall die Methode einer Klasse darüber (oder war's darunter) nicht mißbraucht werden kann. Code habe ich dazu ja gepostet.
Ich meinte genau den Fall dass eine konkrete Klasse von einer anderen konkreten Klasse abgeleitet ist. Du hast in deinem "Punkt 2" angesprochen man könnte ja den copy-ctor verwenden, ich wollte nur darauf hinweisen dass die Verwendung des copy-ctor eben in genau dem Fall zu slicing führen kann.
Weiters wundert es mich etwas dass du den Punkt anführst jmd. könnte vergessen die clone oder clone_impl Funktion zu überschreiben, da du mir ja einmal bei ähnlichen Bedenken vorgehalten hast das sei bloss ein "social engineering" Problem. Liegt hier auch nicht anders. (Davon abgesehen dass der Begriff IMO in beiden Fällen falsch angewendet ist)
Deine "slicing freie" Version funktioniert im Übrigen auch nur bis dorthin wo jmd. die "clone" Methode falsch implementiert (nämlich ohne das cloneObject Template zu verwenden). Wenn man davon dann eine weitere Klasse ableitet die clone nicht selbst implementiert krachts wieder.
-
Guten Morgen,
danke für die vielen Ideen...für die, die nachgefragt haben, wie das in C# funktioniert,
Object.MemberwiseClone() erstellt ein neues leeres Objekt von dem Objekt, dass übergeben wurde. (In C# kann man den Klassentyp mit obj.GetType() zur Laufzeit ermitteln und mit Activator.CreateInstanz einen Konstruktor aufrufen, egal ob sichtbar oder nicht). Anschließend werden alle Felder des Ursprungsobjekt auf das neue Objekt kopiert. (Ohne das, das in einem Copy-Konstruktor oder einer Clone-ähnlichen-Methode realisiert werden muss.)
-
hustbaer schrieb:
@~john:
Weiters wundert es mich etwas dass du den Punkt anführst jmd. könnte vergessen die clone oder clone_impl Funktion zu überschreiben, da du mir ja einmal bei ähnlichen Bedenken vorgehalten hast das sei bloss ein "social engineering" Problem. Liegt hier auch nicht anders. (Davon abgesehen dass der Begriff IMO in beiden Fällen falsch angewendet ist)Ja, in beiden Fällen ist das der Fall, und ja ich sehe das als ein social enginering Problem, denn es gibt Kollegen die nicht so sonderlich achtsam sind, um das Problem zurückhaltend zu umschreiben.
Deine "slicing freie" Version funktioniert im Übrigen auch nur bis dorthin wo jmd. die "clone" Methode falsch implementiert (nämlich ohne das cloneObject Template zu verwenden). Wenn man davon dann eine weitere Klasse ableitet die clone nicht selbst implementiert krachts wieder.
Stimmt, aber Cut&Paste ist eine sehr beliebte "Programmiertechnik", somit hat man das Risiko minimiert. In der Ursprungsfassung hatte ich dies nicht eingebaut, so minimiert man die Eigenleistung für diese Methode nur mit einer Template Klasse kann man das Problem wirklich umschiffen, aber kommen andere Dinge ins Spiel.
-
hustbaer schrieb:
@~john:
Ich meinte genau den Fall dass eine konkrete Klasse von einer anderen konkreten Klasse abgeleitet ist. Du hast in deinem "Punkt 2" angesprochen man könnte ja den copy-ctor verwenden, ich wollte nur darauf hinweisen dass die Verwendung des copy-ctor eben in genau dem Fall zu slicing führen kann.
Was meinst Du konkret damit?
Oder ist es der von mir skizzierte Fall gemeint?
-
~john schrieb:
hustbaer schrieb:
@~john:
Ich meinte genau den Fall dass eine konkrete Klasse von einer anderen konkreten Klasse abgeleitet ist. Du hast in deinem "Punkt 2" angesprochen man könnte ja den copy-ctor verwenden, ich wollte nur darauf hinweisen dass die Verwendung des copy-ctor eben in genau dem Fall zu slicing führen kann.
Was meinst Du konkret damit?
Oder ist es der von mir skizzierte Fall gemeint?Ja, der von dir skizzierte Fall war das was ich immer schon meinte. Dort kann es eben wie erwähnt zu slicing kommen, wenn man den copy-ctor verwendet.
-
OT: Das ist typisch C++ Programmierer. Ueber sowas triviales wie das Clonen von Objekten koennen sie 10 Seiten fuellen.
