[Designproblem] Nachfahren und Operatoren der Vorfahren
-
Hi all,
Hab wieder ein kleines Problem.
folgendes Szenario :Klasse A :
Führt Operatoren ein; auch "Global" damit der Umgang mit primitiven( zb int ) gewährleistet wird...class A { ... A operator+(A const& rhs)const; ... }; ... A operator*( A const& lhs , int const& rhs ); A operator*(int const& rhs , A const& lhs );Klasse B :
Erbt von Klasse A und führt unäre operatoren wie zb 'operator~' die das Komplement des Objektes zurückgeben
class B : public A { ... B operator~(); };Klasse C ,D ,E , F :
Erben von Klasse B führt weitere Funktionen und operatoren* ein ...class C : public B { ... C operator*(C const& rhs)const; C operator*(D const& rhs)const; F operator*(E const& rhs)const; };nun sind Konstrukte wie diese nicht möglich :
C c1; C c2; c1 = c1 * ~c2; // geht nicht da ~c2 ein objekt von Klasse B zurückgibt...Mir fallen spontan 3 "schein" Lösungen ein.
Man könnte in der Klasse B einen Umwandlungsoperator definieren.
Dazu müsste man folglich auch einen für jeden nachfahren definieren, was m.E. alles andere als elegant wäre.
Außerdem ist es anschließend unklar in welche Klasse das jeweilige Objekt "zurückgecastet" werden müsste.Schreibt man den operator* in den nachfahren-klassen(C,D,E, etc.) so um, das er als Argument auch B-Objekte annimmt, wird es semantisch unkorrekt, da es auch andere nachfahren gibt die anderen Multiplikationsregeln folgen.
Man löst die Vererbung auf

Ich wäre für jegliche Anregungen sehr dankbar
-
Führt B (und alle Nachfahren) denn auch neue Attribute ein?
Wenn C mehr Attribute hat als B, dann hat das ohnhin wenig Sinn. Analoges gilt für alle weiteren nachfahren.
-
naja du musst ind em Zusammenhang halt wissen was ~c2 sein soll - ein B kommt raus, für deine Multiplikationen brauchst du aber ein C oder ein D.
Die Entscheidung kann dir der Compiler nicht abnehmen. Die Frage ist ja, "was ist das Komplement eines C?" Vermutlich wieder ein C, dann bräuchtest du einen C::op~, der eben ein C zurückgibt. Du kannst darin auf den geerbten B::op~ zugreifen, indem du ihn qualifiziert aufrufst.
-
Tachyon schrieb:
Führt B (und alle Nachfahren) denn auch neue Daten ein?
Nein. Alle Klassen sind PODs die keine weiteren Daten einführen( damit sie ua in unions anderer klassen verwendet werden können).
pumuckl schrieb:
naja du musst ind em Zusammenhang halt wissen was ~c2 sein soll - ein B kommt raus, für deine Multiplikationen brauchst du aber ein C oder ein D.
Die Entscheidung kann dir der Compiler nicht abnehmen. Die Frage ist ja, "was ist das Komplement eines C?" Vermutlich wieder ein C,
Richtig erkannt... B it ledeglich eine Abstraktionsebene die den Code zb zum komplementieren weitervererbt...
pumuckl schrieb:
dann bräuchtest du einen C::op~, der eben ein C zurückgibt. Du kannst darin auf den geerbten B::op~ zugreifen, indem du ihn qualifiziert aufrufst.
hmm ja stimmt, das wäre eine Lösung. Aber dann müsste ich Alle Operatoren(und Funktionen) von A in B neu implementieren und diese wiederum in C und allen nachfahren auf gleiche weise reimplementieren. Als Tippfauler Programmierer gefällt mir das nicht...
-
Speed schrieb:
Tachyon schrieb:
Führt B (und alle Nachfahren) denn auch neue Daten ein?
Nein. Alle Klassen sind PODs die keine weiteren Daten einführen( damit sie ua in unions anderer klassen verwendet werden können)
Dann würde ich das nicht mit Vererbung lösen. Auch der Ansatz, dass erbende Klassen andere Regeln für Multiplikation haben sollen, ist meiner Meinung nach ein Designfehler.
Wenn B von A erbt, dann sollte sich B genauso verhalten wie A. Dem ist aber nicht mehr so, wenn B * B was anderes ergibt als A * A.
-
A operator*( A const& lhs , int const& rhs ); A operator*(int const& rhs , A const& lhs ); c1 = c1 * ~c2; // geht nicht da ~c2 ein objekt von Klasse B zurückgibt...Was sagt denn der Compiler? Welche Fehlermeldung? Nach der besagten Operation erhaelt man ein Object vom Typ A. Beruecksichtigt man noch die anderen Regeln, so verwundert es mich, dass der Kompiler nicht total verwirrt ist.
-
Speed schrieb:
hmm ja stimmt, das wäre eine Lösung. Aber dann müsste ich Alle Operatoren(und Funktionen) von A in B neu implementieren und diese wiederum in C und allen nachfahren auf gleiche weise reimplementieren. Als Tippfauler Programmierer gefällt mir das nicht...
Schau dir evtl. mal boost::operators an. Ich hab da hier im Magazin auch zwei Artikel drüber geschrieben:
Einführung in boost::operators
boost::operators für Fortgeschrittene
Damit könntest du dir evtl. eine ganze Menge Arbeit sparen.
-
Tachyon schrieb:
Wenn B von A erbt, dann sollte sich B genauso verhalten wie A. Dem ist aber nicht mehr so, wenn B * B was anderes ergibt als A * A.
A * A gibt es nicht ...
Die Klassen redefinieren die Operatoren nicht , jede Vererbung führt weitere ein...
Konkrete Beispiele für die dritte vererbungsebene wären
- Quaternionen mit reinem komplexen inhalt
- Quaternionen mit dualen oder Double-zahlen
- duale Vektoren , komplexe Vektorenalle haben einige gemeinsamkeiten zb die Komplementierung die in 'B' eingeführt wird; Multiplikation wird erst in der dritten ebene eingefürt( da sie sich überall unterscheidet).
-
Also werden (anscheinend in der dritten Ebene) doch Attribute hinzugefügt. Wie soll denn z.B. das Komplement (welches ja auf der Ebene B arbeitet) für komplexe Vektoren aussehen? An der Stelle, an welcher der Komplement-Operator definiert wird, gibt es ja noch gar keine komplexen Vektoren.
-
Die einzigen attribute die eingeführt werden sind in A nämlich ein array das verwaltet wird...
Für A gibt es kein Komplement...
B ist eine Spezialisation von A(also auch ein A).Esn fasst alle gemeinsamen funktionen von C&co zusammen ...C&Co (vorhin genannte beispiele) besitzen gemeinsamkeiten die in B definiert sind und leiten weitere , für die klasse typische verhaltensweisen ein...
so stellt B einen Vector da der nicht im gewohnten zahlenbereich liegt...
Komplexe und duale(und andere "derivate") haben diese gemeinsamkeit; die multiplikationsregel untgerscheiden sich allerdings..(so ist bekannt, dass i*i ) -1 und d * d = 0 ist)
theoretisch könnte ich B auflösen und in C&Co den Code direkt übernehmen..würde mein Problem lösen; ist aber unschön da das überall der gleiche code ist...
@pumukl : schau ich mir an ..sieht auf den ersten Blick vielversprechend aus; (ist aber meine Klasse immer noch ein POD ?)
EDIT : @Tachyon : das komplement wird erreicht, wenn man lediglich den NICHT-REELLEN teil Komplementiert -> also ein Minus davor stehlt...
Sei float V[4] ein "nicht-reeller" vector so hat er ein reellen anteil und einen nicht reelle anteil( was bei mir die letzten drei Elemente sind)also sähe das komplement folgender massen aus :
Komplement[0] =V[0]
Komplement[1] = -V[1]
Komplement[2] = -V[2]
Komplement[3] = -V[3]
-
Operatorüberladung und Vererbung passen regelmäßig nicht gut zusammen. Möglich wäre, implizite Konvertierungen von B nach C,D,E zu ermöglichen - dass das keine gute Idee ist, muss sicher nicht erläutert werden.
Ansonsten ist eine eigenständige Funktion für jede Ableitung von B erforderlich. Die Struktur dieser Funktion hängt allerdings nicht von der konkreten Ableitung ab, somit bietet sich ein Funktionstemplate an.template <typename T> typename enable_if<is_convertible<T&,B&>,T>::type operator~(T rhs) { B& b = rhs; b = ~b; return rhs; }Speed schrieb:
Tachyon schrieb:
Führt B (und alle Nachfahren) denn auch neue Daten ein?
Nein. Alle Klassen sind PODs die keine weiteren Daten einführen( damit sie ua in unions anderer klassen verwendet werden können)
Ein POD enthält niemals Basisklassen, und eine Verwendung in unions ist erst recht ausgeschlossen.
-
camper schrieb:
Operatorüberladung und Vererbung passen regelmäßig nicht gut zusammen. Möglich wäre, implizite Konvertierungen von B nach C,D,E zu ermöglichen - dass das keine gute Idee ist, muss sicher nicht erläutert werden.
Ansonsten ist eine eigenständige Funktion für jede Ableitung von B erforderlich. Die Struktur dieser Funktion hängt allerdings nicht von der konkreten Ableitung ab, somit bietet sich ein Funktionstemplate an.template <typename T> typename enable_if<is_convertible<T&,B&>,T>::type operator~(T rhs) { B& b = rhs; b = ~b; return rhs; }Sieht aus als ob das währe was ich suche...
Habe ein konstrukt wie dieses noch nie gesehn ; werde mich mal schlau machen wie das genau funktioniert.enable_if scheit nur T zu returnen wenn T in B konventierbar ist - würde zugern wissen wie das genau funktioniert.
-
Speed schrieb:
enable_if scheit nur T zu returnen wenn T in B konventierbar ist - würde zugern wissen wie das genau funktioniert.
google: "boost enable_if" sollte dir alles liefern was du brauchst

-
Speed schrieb:
Mir fallen spontan 3 "schein" Lösungen ein.
Templates sind mir da direkt eingefallen.
Weiterhin geht das glaub ich sehr in die Richtung "policy-based design".Meiner Meinung nach brauchst du dafür nicht zwingend boost::enable_if. Müßte alles mit normalen "Hausmitteln" sofort gehen.
Falls du noch nie mit Templates gearbeitet hast, solltest du dich da mal dringend reinarbeiten. Denn das springt einem doch förmlich ins Auge

-
Vielen Dank
ein "enable_if" marke eigenbau
hat mein Problem gelöst...
Templates waren mir zwar bekannt ( werden massenweise in meinem Projekt verwendet ), allerdings habe ich niemals "typenames" auserhalb der "template-definition" gesehn..Closed