=-Operator bei Vererbung
-
make it right schrieb:
D.h. derselbe Aufwand wie bei einem virtuellen Funktionsaufruf.
Das glaube ich jetzt mal nicht.
-
de scha wü?
-
make it right schrieb:
Klingt komplex. Da wäre ein konkretes Beispiel interessant, um zu sehen, ob das wirklich einfacher ist als die obige Struktur mit assign/swap! Halte ich für nicht möglich, lasse mich aber gern überzeugen.
Ist auch komplex, aber du hast deinen Fehler zur Kompilierzeit
Hier ein Beispiel:
#include<iostream> template<typename T> class Assignable{ public: T& assign(const T& t){ T temp(t); T& tRef = static_cast<T&>(*this); swap(tRef, temp); return tRef; } }; class IA { public: int a; private: IA& operator=(const IA& a); }; class IB : public IA{ public: int b; private: IB& operator=(const IB& a); }; class A :public IA, public Assignable<A>{ public: A& operator=(const A& t){ return assign(t); } }; class B :public IB, public Assignable<B>{ public: B& operator=(const B& t){ return assign(t); } }; void swapInternal(IA& a, IA& b){ std::swap(a.a, b.a); } void swap(A& a, A& b){ swapInternal(a,b); } void swap(B& a, B& b){ swapInternal(a, b); std::swap(a.b, b.b); } int main(){ A a; a.a = 1; B b;; b.a = 1; b.b = 1; A a2; a2 = a; B b2; b2 = b; // b2 = static_cast<IB&>(b); <- Das geht dann nicht!!! return 0; }Die Frage, ob man sich den Aufwand macht, ist natürlich Geschmacksache,
jedoch hat man eine wirklich saubere Schnittstelle...
-
Es ging ja darum, eine polymorphe Zuweisung zu ermöglichen.
Das geht mit deinem Beispiel nicht. Ergänze dein main-Programm umIB& ibref = b2; // legales C++ B b3; b3.a = 4; b3.b = 5; ibref = b3; // nicht möglich mit deinem Modell IA& iaref = b2; // legales C++ iaref = b3; // geht auch nichtDas andere Modell von oben ist nicht nur einfacher, es hat auch kein Problem mit polymorpher Zuweisung, static_cast hin oder dynamic_cast her.
-
Klar kannst du keiner Schnittstellen-Definition eine konkrete Instanz zuweisen
oder andersrum. Deswegen ist mein Ziel eine solche Zuweisung zu verhindern.Wenn du einer abgeleiteten Klasse eine Basisklasse zuweist kannst du natürlich
deren Felder kopieren, aber die zusätzlichen Felder haben ja einen Einfluß auf
das Verhalten der Klasse und werden weiterhin in irgendeiner Art und Weise
berücksichtigt.Ich kann einer Banane die Kalorien etc. eines anderen Obst (z.B. Apfel) zuweisen,
jedoch bleibt sie immer noch eine Banane.
Andersrum kann ich einem Obst die Kalorien einer Banane zuweisen, jedoch bleibt
mein Objekt immer noch z.B. ein Apfel.Ich kann ja nicht das komplette Objekt 'transformieren'. Wenn man sowas machen
würde/könnte, hätte man plötzlich in seinen Korb mit Äpfeln eine Banane...
-
Alles Banane. ibref hat den statischen Typ IB (Sicht des Compilers), aber den dynamischen Typ B (Sicht zur Laufzeit). Unterschiedliche statische und dynamische Typen sind gerade ein Wesenszug des Polymorphismus!! (nachlesen!!!). Damit repräsentiert die Referenz ibref ein vollwertiges B-Objekt, dem man selbstverständlich ein anderes B-Objekt zuweisen kann. Also Apfel = Apfel.
Und für eine nicht-virtuelle Zuweisung braucht man keine Extra-Templates.
-
Man könnte denken, dass ein virtueller Zuweisungsoperator genügen würde. Das ist nicht der Fall, weil der Zuweisungsoperator anders als "normale" virtuelle Funktionen aufgerufen wird. Wenn nur ein virtueller Zuweisungsoperator geschrieben wurde, wird bei der Compilation der nicht-virtuelle automatisch auch noch erzeugt - und ggf. statt des virtuellen aufgerufen (siehe Abschnitt 13.5.3 im Standardentwurf). Das ist kein Problem, wenn die abgeleitete Klasse keinerlei dynamische Daten hat. Wenn es jedoch Zeigerattribute gibt, denen mit new etwas zugewiesen wird, ist ein selbstgeschriebener nicht-virtueller Zuweisungsoperator unumgänglich, weil der compilergenerierte nur den Zeiger kopieren würde, ohne Speicherplatz zu beschaffen.
Damit scheint es am einfachsten, den Zuweisungsoperator bei dynamischen Attributen in jeder Klasse der Vererbungshierarchie zu definieren und in ihm eine virtuelle Methode assign() aufzurufen. Weil der polymorphe Aufruf dafür sorgt, dass sowieso die richtige der überschriebenen assign()-Methoden aufgerufen wird, reicht ein static_cast! In diesem Punkt hat CSpille recht. Der dynamic_cast wirft im Zuweisungsoperator keine Exception bei der Zuweisung
base = derived;weil der Typ ja stimmt. Das Problem liegt woanders: In C++ ist diese Zuweisung erlaubt, kopiert wird aber nur der Basisklassenanteil (object slicing). Die Methode assign() der obersten Basisklasse sei
virtual Base& assign(const Base& rhs) { Base temp(rhs); swap(temp); return *this; }Der Kopierkonstruktor kopiert hier nur den Basisklassenanteil.
Wenn sichergestellt werden soll (und nur dann!), dass die Zuweisung base = derived; als Fehler betrachtet werden soll, weil es keine sinnvolle praktische Anwendung dafür gibt (Gegenbeispiel gesucht...), müssen die Objekttypen links und rechts verglichen werden, alsovirtual Base& assign(const Base& rhs) { if(typeid(*this) != typeid(rhs)) { throw std::bad_typeid(); } Base temp(rhs); swap(temp); return *this; }Wenn diese Typprüfung gewünscht ist, muss sie in den assign()-Methoden der abgeleiteten Klassen wiederholt werden.
typeid ist von der Performance her vermutlich etwa wie dynamic-cast anzusehen. Ob der Performance-Aspekt von typeid überhaupt bei der Zuweisung eine wichtige Rolle spielt, kann nur im Vergleich mit der Performance des Kopierkonstruktors beurteilt werden.
Ein vergleichbares Problem hat man beim Vergleichsoperator und dem Vergleich (base == derived). Ohne Typprüfung wäre der Vergleichsoperator nicht symmetrisch, d.h. (base == derived) kann true sein und das umgekehrte false (wobei dann ohne Typprüfung auf undefinierte Bereiche zugegriffen wird). Weil der Vergleich in abgeleiteten Klassen die Basisklasse einschließt, muss die Prüfung mit typeid in abgeleiteten Klassen nicht wiederholt werden; im Gegensatz zur Zuweisung, wo die Prüfung, sofern gewünscht, in jeder Klasse vorgenommen werden muss, weil assign() nicht die entsprechende Oberklassenmethode aufruft. Also etwa
virtual bool Base::operator==(const Base& arg) const { return typeid(*this) == typeid(arg) && // Zeile löschen bei nicht erwünschter Prüfung vergleichKlassenlokalerAttribute(arg); // gibt bool zurück }bzw. in einer abgeleiteten Klasse
bool Derived::operator==(const Base& arg) const { const Derived& rarg = static_cast<const Derived&>(arg); return X::operator==(arg) && // X = *direkte* Oberklasse (ggf. Base) vergleichKlassenlokalerAttribute(rarg); // gibt bool zurück }
-
@UBr: Durch die Zusätzliche Vererbungsschicht ohne operator= möchte ich genau
diese Zuweisungsproblematik beseitigen. So eine Zuweisung ist in meinen Augen
nämlich kein Runtime-Fehler. Sonst kann man auch vorher casten.EDIT: In den meisten Fällen würde ich jedoch wegen des enormen Aufwandes auf ein
korrekte Anwendung des Zuweisungsoperstors im Programm achten. Ich wollte nur eine
theoretisch saubere Lösung vorschlagen.
Einen dynamic_cast im Debug und static_cast im Release bei Verwendung
eines Templates finde ich in der Regel aber ausreichend. So sieht man, wenn auch
erst zur Laufzeit, dass etwas schief geht.Oder willst du bei beiden nicht genau wissen von welchem Typ sie sind ^^
UBr schrieb:
virtual Base& assign(const Base& rhs) { if(typeid(*this) != typeid(rhs)) { throw std::bad_typeid(); } Base temp(rhs); swap(temp); return *this; }Wenn beide von der gleichen abgeleiteten Klasse sind, dann ist die Zuweisung
erlaubt und kopiert nur das 'halbe' Objekt?
Oder wie soll ich das verstehen?Gibt es im Standard eigentlich typeid?
zamborambo schrieb:
Und für eine nicht-virtuelle Zuweisung braucht man keine Extra-Templates.
Ich denke das Template ist hier extrem sinnvoll, ansonsten bräuchte ich den
Code in jeder abgeleiteten Klasse.
Genau wegen des oben bestehenden Problems verwende ich Templates...
-
Ich verstehe den Bedarf gar nicht. Typverändernde Zuweisungen mache ich nicht auf Objekten, zum Beispiel weil das eh zu selten klappt, sondern auf Basisklassenzeigern, und auf einmal ist es logisch, einfach, schnell und einfach.
-
volkard schrieb:
Ich verstehe den Bedarf gar nicht. Typverändernde Zuweisungen mache ich nicht auf Objekten, zum Beispiel weil das eh zu selten klappt, sondern auf Basisklassenzeigern, und auf einmal ist es logisch, einfach, schnell und einfach.

Genau aus diesem Grund benutze ich sehr selten Collections (Ausnahme: einfache Datentypen),
die keine Zeiger besitzen.In den Anfängen meiner C++-Programmierung habe ich gerne von so einem
Template geerbt, damit ich gesehen habe, was schief läuft.Inzwischen verlasse ich mich aber komplett darauf, dass ich solche
'Zuweisungsfehler' nicht in meinem Code habe.