Operator Overloading - Effizient oder Nicht?
-
EffizientOrNot? schrieb:
gibts denn keinen anderen, besseren Weg?
1) Als erstes würde ich die Operatoren global machen. Es besteht kein Grund für Memberfunktionen, über
GetZaehler()undGetNenner()auch von aussen auf die Attribute zugreifen kannst. Globale Funktionen erhöhen die Kapselung (weniger Funktionen haben direkten Zugriff auf private Member), im Falle der Operatoren kann eine implizite Konvertierung des ersten Elements stattfinden. Wenn du zudem plötzlich eine Überladung definierst, sodassoperator<z.B. mitintsymmetrisch funktioniert, musst du zwangsläufig zu globalen Funktionen greifen. Siehe dazu auch Operatorüberladung in C++.2) In deinem Beispiel kann man unnötige Codeduplizierung ausserdem vermeiden, indem man bedenkt, wie relationale Operatoren durch einander ausgedrückt werden können. Es reicht, wenn du
operator<undoperator==vollständig implementierst.bool operator> (const Bruch& lhs, const Bruch& rhs) { return rhs < lhs; } bool operator<= (const Bruch& lhs, const Bruch& rhs) { return !(rhs < lhs); } bool operator>= (const Bruch& lhs, const Bruch& rhs) { return !(lhs < rhs); } bool operator!= (const Bruch& lhs, const Bruch& rhs) { return !(lhs == rhs); }3) Unter der Annahme, dass
GetZaehler()undGetNenner()integrale Werte zurückgeben, sind deine Vergleichsfunktionen schlecht implementiert, da durch die Integer-Divisionen Informationen verloren gehen. Bedenke, dass bei Integer-Typen beispielsweise die Identität5/2 == 4/2zutrueevaluiert. Du müsstest also mindestens einen der Divisionsoperanden vor der Operation casten.4) Performancetechnisch betrachtet wäre es wahrscheinlich schneller, du würdest die Division in eine Multiplikation umwandeln (Gleichung
a/b > c/dzua*d > c*bumformen, Vorzeichen beachten). Damit bräuchtest du auch keine Fliesskommatypen. Allerdings besteht die Gefahr eines Overflows.
-
Danke Vielmal für die vielen hilfreichen Informationen!
-
Es reicht, wenn du operator< und operator== vollständig implementierst.
Das kann keiner wissen. Manchmal hat man keine so sauberen Ordnungen, zB in IEEE 754. Da ist
x < NaNfalsch, aber auchNaN <= x. In solchen Fällen muss man mindestens operator< und operator== und operator<= festlegen.

-
Habe mal den Artikel durchgeblättert und weiss nun weshalb ich freie Funktionen verwenden sollte.
Doch wie kann ich eine freie Funktion machen, die entweder zwei Objekte Bruch oder ein Bruch und Int nimmt??
-
EffizientOrNot? schrieb:
Doch wie kann ich eine freie Funktion machen, die entweder zwei Objekte Bruch oder ein Bruch und Int nimmt??
Indem du sie einfach so definierst? Da wird dann schon die passende Funktion aufgerufen. Dazu solltest du aber vlt. noch den Konstruktor von Bruch
explicitmachen.
-
Das bedeutet ich schreibe für jeden Operator zwei Funktionen?
Nämlich eine für zwei Brueche und eine für Bruch und ein Template für int, float, etc.?
-
µngbd schrieb:
Es reicht, wenn du operator< und operator== vollständig implementierst.
Das kann keiner wissen. Manchmal hat man keine so sauberen Ordnungen, zB in IEEE 754. Da ist
x < NaNfalsch, aber auchNaN <= x. In solchen Fällen muss man mindestens operator< und operator== und operator<= festlegen.

nexus' punkt fing ja nun auch an mit "in deinem beispiel"... das das nicht immer der fall ist, sollte klar sein...
dein vergleich is btw doof gewählt - lässt sich immernoch mit nem early return lösen...
@drakon:
hier würde ich den ctor aber nichtexplicitmachen - oder hast du irgend nen argument für die nicht implizite typumwandlung?@EffizientOrNot?:
"Nämlich eine für zwei Brueche und eine für Bruch und ein Template für int, float, etc.?"
nö... du bietest entsprechende nicht-explizite konstruktoren an und brauchst dann nur noch einen einzigen op< zu implementieren(nämlich einen, der 2 brüche vergleicht)...bb
-
@drakon:
hier würde ich den ctor aber nicht explicit machen - oder hast du irgend nen argument für die nicht implizite typumwandlung?Ich dachte er wollte das eben genau nicht, sondern eben verschiedene Funktionen. Aber dem fall will er das ja doch nicht, dann können wir den ctor ohne explicit lassen.
@EffizientOrNot:
Wenn du den ctor so lässt, dann wird die Umwandlung eines integers in einen Bruch automatisch gemacht und du musst nix spezielles machen. Ein Bruch mit floats/doubles drin macht imo überhaupt keinen Sinn (denk da mal drüber nach).
-
Das ist ja total cool.
Heisst das, sobald ich ein Integer zu meiner Operator-Funktion schicke, obwohl dort ein Bruch erwartet wird, wird dieser Integer als Argument an meine Bruch Klasse geschickt?
Oder wieso funktioniert das x_X?
-
Ja, weil du im Konstruktor von Bruch ja sagst, dass ein integer in einen Bruch "umgewandelt" werden kann. Das macht ja auch schon rein mathematisch einen Sinn, weil du ja überall, wo ein Bruch erwartet wird einfach die Zahl/1 übergeben kannst. Ist ja ein völlig korrekter Bruch und entspricht auch immer noch der gleichen Zahl. Das schöne ist auch, dass du jetzt zuerst einen integer und dann einen Bruch schreiben kannst (was nicht möglich wäre, wenn die Funktion nicht frei implementier worden wäre).
Das sollte aber alles im verlinkten Artikel über Überladung genannt werden.
-
EffizientOrNot? schrieb:
Das ist ja total cool.
Heisst das, sobald ich ein Integer zu meiner Operator-Funktion schicke, obwohl dort ein Bruch erwartet wird, wird dieser Integer als Argument an meine Bruch Klasse geschickt?
Hast Du meine Antwort eigentlich gelesen?
Gruß,
SP
-
Sebastian Pizer schrieb:
EffizientOrNot? schrieb:
Das ist ja total cool.
Heisst das, sobald ich ein Integer zu meiner Operator-Funktion schicke, obwohl dort ein Bruch erwartet wird, wird dieser Integer als Argument an meine Bruch Klasse geschickt?
Hast Du meine Antwort eigentlich gelesen?
Gruß,
SPNatürlich! Es hat mir nun gleich das Aha-Erlebnis gegeben
(Mit dem Post von drakor D:)Vielen Dank an Alle, habt mir echt geholfen

Bye Bye
