Hi,
ich hoffe ihr könnt mir helfen.
Folgendes Problem:
Ich soll den '+'-Operator in einer abgeleiteten Klasse überladen. Der
Prototyp ist vorgegeben mit:
Dieser steht in meiner header-Datei FRACTION3.h.
Jetzt benötige ich noch die Anweisung welche Berechnungen er genau
durchführen soll. Dies sollte doch in meiner FRACTION3.cpp-Datei (oder
einer anderen) stehen, soweit richtig oder?
Die Anweisung lautet bei mir zurzeit wie folgt:
bekomme ich die Fehlermeldung "must take either zero or one argument".
Sämtliche Vergleichsoperatoren konnte ich problemlos überladen, aber
hier klemmt es momentan bei meiner Ausführung.
Ich hab schon gegoogelt, aber seit einer Stunde ist irgendwie Stillstand
beim Programmieren.
Mir geht es hauptsächlich erst einmal darum das jetzt richtig
einzubinden, sollten da noch weitere Fehler bezüglich der Referenz und
Co. sein kann ich mich später selber darum noch kümmern.
Vielleicht kann mir jemand etwas Licht ins Dunkle bringen.
Man kann Operatoren auf zwei Arten überladen:
1. global
2. als Methode in der Klasse des ersten Operanden
Im Fall 1 steht die Operatordefinition außerhalb jeder
Klasse und bekommt alls Operanden (1 oder 2, je nachdem)
als Parameter.
Bei 2. steht der Operator als Methode in der Klasse,
*this ist der erste Operand und ggf. kommt ein zweiter
als Parameter über die Parameterliste.
Ich vermute, daß es in der Richtung hakt, habe aber dein
Problem nicht recht verstanden.
Eine Meldung "multiple definitions of..." kenne ich nicht.
Wenn du nicht die echte Fehlermeldung und einen verwertbaren
(und lesbaren) Quelltext zeigst, kann man nicht viel sagen.
(Die Variante 2. ist übrigens im Zweifel zu bevorzugen.)
Die Fehlermeldung taucht beim kompillieren der *.cpp auf, und lautet bei
dieser Konstellation:
"FRACTION3 FRACTION3::operator+(const FRACTION3 &, const FRACTION3
&)must take either zero or one argument"
"no 'FRACTION3 FRACTION3::operator+(const FRACTION3 &, const FRACTION3
&)' member function declared in class 'FRACTION3'"
"[Build Error] [fraction3.o] Error 1"
Die zweite Variante hab ich auch realisiert, siehe header. Diese
funktioniert auch, aber die Vorgabe ist wie gesagt den Prototyp mit zwei
Parametern zu verwenden.
Du willst also den Operator als globale Funktion definieren
(meine Variante 1.) und nicht als Methode (meine 2)?
Dann ist es eine globale Funktion.
Du schreibst aber:
Christian Funke schrieb:> Jetzt benötige ich noch die Anweisung welche Berechnungen er genau> durchführen soll.
Das nennt sich übrigens Definition der Funktion.
Christian Funke schrieb:> Dies sollte doch in meiner FRACTION3.cpp-Datei (oder> einer anderen) stehen, soweit richtig oder?
Ja.
Die zweite Variante ist von der Aufgabenstellung her nicht gewollt.
ABER:
Ich bedanke mich Klaus, nun funktioniert es. War wohl zu fixiert auf
meine Klasse.
Ich wünsche allen noch einen schöne und erholsame Nacht.
Christian Funke schrieb:> Die zweite Variante ist von der Aufgabenstellung her nicht gewollt.
Das ist auch gut so.
operator+ wird besser als globaler Operator implementiert.
Es ist schliesslich nicht wirklich einzusehen, warum in
1
FRACTION3a(5),b;
2
3
b=a+10;// I
4
b=10+a;// II
Statement I funktioniert und Statement II nicht kompiliert, wie es bei
einem Member-Operator der Fall wäre. Mit einem globalen Operator geht
aber beides.
Da hast du jetzt aber etwas an den Haaren hergezogen, oder ich
habe dich mißverstanden.
FRACTION3+int ebenso wie int+FRACTION3 haben doch erstmal nicht
damit zu tun, wie man FRACTION3+FRACTION3 implementiert?
Dafür müsste man entweder die passenden Operatoren auch noch
überladen (bei int+FRACTION3 zwangsläufig global), oder passende
Konvertierungen schreiben - dann wiederum wird der Operator
für FRACTION3+FRACTION3 aufgerufen werden, den man global oder
als Methode implementieren kann.
Klaus Wachtler schrieb:> Da hast du jetzt aber etwas an den Haaren hergezogen, oder ich> habe dich mißverstanden.
Wahrscheinlich
>> FRACTION3+int ebenso wie int+FRACTION3 haben doch erstmal nicht> damit zu tun, wie man FRACTION3+FRACTION3 implementiert?
Doch.
Ein globaler operator+ (zusammen mit einem Konstruktor) kann alle 3
Fälle abdecken. Und zwar OHNE dass du explizit die 10 in ein FRACTION3
Objekt verwandelst, das kann dann der Compiler von alleine machen. Dazu
muss der Operator aber global sein, damit er als Kandidat überhaupt in
Frage kommt, denn bei
10 + a
würden nur Member-Operatoren von int abgeklappert werden (wenn es die
geben würde). Die Member Operatoren von a sind uninteressant, da a ja
höchstens Argument für so einen Operator sein könnte.
Den globalen Operator kann der Compiler aber nehmen, denn 1 Argument
(das zweite) passt perfekt (*). Er muss nur noch einen Weg finden, wie
er aus dem int ein FRACTION3 Objekt machen kann. Und da es einen
Konstruktor gibt, der das kann, benutzt ihn der Compiler auch.
Damit hat er beide Argumente fertig, um den globalen op+ einzusetzen.
Du kannst tatsächlich
b = 10 + a;
schreiben, und es wird dafür der globale op+ aufgerufen, nachdem der
Compiler aus den 10 ein temporäres FRACTION3 Objekt gemacht hat.
Probiers aus, wenn du mir nicht traust :-)
(*) Das klingt jetzt nach einer seltsamen Begründung. Ist es auch.
Tatsächlich stellt sich raus, dass das wie bei anderen Fällen auch ist,
in denen es mehrere Kandidaten für einen Funktionsaufruf gibt, aus denen
der Compiler auswählen muss: Derjenige mit den wenigsten Konvertierungen
gewinnt.
So richtig interessant wird das Ganze dann, wenn man der Klasse dann
auch noch einen Konvertier Operator verpasst, so dass man aus einem
FRACTION3 Objekt einen int (oder double) erzeugen kann. Der würde dann
bei
int c = a;
zum Einsatz kommen.
Jetzt gibt es dann 2 Möglichkeiten, wie
b = 10 + a;
compiliert werden könnte.
* Aus 10 ein FRACTION3 Objekt machen und den
op+( const FRACTION3&, const FRACTION& )
benutzen. Das Ergebnis dann mit dem FRACTION3::op= an b zuweisen
* Oder aber aus a einen int machen, eine int Addition benutzen, aus
dem Ergebnis wieder mit einem Konstruktor ein FRACTION3 Objekt
machen und das mit dem FRACTION3::op= an b zuweisen
Die eigentlichen Additionen sind gleichwertig, in jedem Fall muss 1
Konvertierung gemacht werden und damit wird der Compiler sich
beschweren, dass das nicht eindeutig ist.
Fazit: Mit zuvielen Konvertier-Operatoren und Konstruktoren kann man
sich ganz schnell in die Bredullie bringen. Man eröffnet damit dem
Compiler zu viele Möglichkeiten. Das ist schlecht, denn
* der Compiler setzt sie schamlos ein, um sich einen Weg zu bauen etwas
trotzdem zu compilieren, was man so eigentlich gar nicht wollte
* Man immer häufiger in nicht entscheidbare Situationen kommt, weil es
mehrere gleich gute Wege zum Compilieren gibt.
In wieder anderen Fällen hab ich zb (in meiner Vector3D Klasse) den
binären op* absichtlich als Member Operator gemacht, damit ich durch die
Schreibweise eine gewisse Kontrolle darüber behalte, wenn er eingesetzt
wird. Bei Vektoren gibt es ja 3 'Multiplikationen':
Skalar-Multiplizieren, Kreuzprodukt und Multiplizeieren mit einem Skalar
um den Vektor zu skalieren. Das war mir zu gefährlich, wenn sich der
Compiler da frei aus dem Fundus bedienen kann und mir simple logische
Fehler compiliert. Dadurch das ich gezwungen bin
b = a * 3;
zu schreiben und
b = 3 * a;
ein Fehler ist, hab ich das bischen Kontrolle bekommen, das dann schon
gereicht hat um nicht ständig auf Tippfehler reinzufallen.
Karl heinz Buchegger schrieb:> Du kannst tatsächlich>> b = 10 + a;>> schreiben, und es wird dafür der globale op+ aufgerufen, nachdem der> Compiler aus den 10 ein temporäres FRACTION3 Objekt gemacht hat.>> Probiers aus, wenn du mir nicht traust :-)
Doch das traue ich dir zu :-)
Wenn du genau das so haben willst, ist ein globaler Operator wohl
sinnvoller. Daß es solche Fälle gibt, weiß ich und behaupte auch
nicht, daß man ihn nie global definieren dürfte. Nur im Zweifelsfall
ist es besser, ihn lokal zu definieren, wenn nichts anderes dagegen
spricht, weil die globalen Operatoren zwangsläufig auch ihre Nachteile
haben.
Ich bin mir aber nicht sicher, ob man das obige Beispiel überhaupt so
stehen lassen sollte.
Es geht darum, 2 Zahlen unterschiedlichen Typs zu addieren.
Das kann gehen, indem man die 10 in ein FRACTION3 konvertiert und zwei
solchige addiert, oder indem man aus a eine int macht und eine
int-Addition durchführt.
Daß hier nur eines von beiden beabsichtigt sein wird, ist klar, aber
das geht aus dem Ausdruck nicht hervor.
Da halte ich es für deutlich besseren Stil, klar zu sagen, was man
will:
1
b=FRACTION3(10)+a;
oder:
1
b=10+int(a);
Damit entfallen alle Fehler, die man sich (oder anderen, weniger
begabten Mitstreitern) einbrockt, indem man sich auf eine bestimmte
Suchreihenfolge der Operatoren verlässt.
Schließlich sind derart übersichtliche Fälle nicht unbedingt die
Regel; spätestens mit Ableitungen wird es spannender, oder mit
meinen geliebten templates.
Karl heinz Buchegger schrieb:> Fazit: Mit zuvielen Konvertier-Operatoren und Konstruktoren kann man> sich ganz schnell in die Bredullie bringen. Man eröffnet damit dem> Compiler zu viele Möglichkeiten. Das ist schlecht, denn> * der Compiler setzt sie schamlos ein, um sich einen Weg zu bauen etwas> trotzdem zu compilieren, was man so eigentlich gar nicht wollte> * Man immer häufiger in nicht entscheidbare Situationen kommt, weil es> mehrere gleich gute Wege zum Compilieren gibt.
Genau!
Karl heinz Buchegger schrieb:> In wieder anderen Fällen hab ich zb (in meiner Vector3D Klasse) den> binären op* absichtlich als Member Operator gemacht, damit ich durch die> Schreibweise eine gewisse Kontrolle darüber behalte, wenn er eingesetzt> wird. Bei Vektoren gibt es ja 3 'Multiplikationen':> Skalar-Multiplizieren, Kreuzprodukt und Multiplizeieren mit einem Skalar> um den Vektor zu skalieren. Das war mir zu gefährlich, wenn sich der> Compiler da frei aus dem Fundus bedienen kann und mir simple logische> Fehler compiliert. Dadurch das ich gezwungen bin>> b = a * 3;>> zu schreiben und>> b = 3 * a;>> ein Fehler ist, hab ich das bischen Kontrolle bekommen, das dann schon> gereicht hat um nicht ständig auf Tippfehler reinzufallen.
Das funktioniert aber dann auch nur, solange du damit alleine
unterwegs bist und dir bewusst bist, daß a*3 und 3*a grundverschiedene
Dinge sind.
Da sehe ich bereits das Ende der Automatismen erreicht, die man mit
C++ und überladenen Operatoren hat, weil die Operatoren auf die
üblichen Typen abzielen und in ihrer Syntax gar nicht und in der
Semantik nur sehr bedingt sinnvoll zu ändern sind.
Es geht bereits los damit, daß vektor*vektor sich nur auf eines von
zwei möglichen Produkten beziehen kann und es einen operator X halt
nicht gibt, und geht mit a*3 ungleich 3*a weiter.
Irgendwelche subtilen Unterschiede sehr ähnlicher Ausdrücke zu nutzen,
artet leicht in Mißbrauch aus.
Solange man weiß, was man tut, kann man es natürlich machen.
Generell empfehlen würde ich aber eher nicht.
Klaus Wachtler schrieb:> Generell empfehlen würde ich aber eher nicht.
Nein, natürlich nicht.
Das war auch eher eine Krücke, auf die sich die Entwicklungsmannschaft
einigen konnte. Wenn man sich mal dran gewöhnt hat, ist es 'sehr
natürlich' (wie fast alles im Leben)
> Daß hier nur eines von beiden beabsichtigt sein wird, ist klar, aber> das geht aus dem Ausdruck nicht hervor.> Da halte ich es für deutlich besseren Stil, klar zu sagen, was man> will: b = FRACTION3(10) + a;>>> oder: b = 10 + int(a);>>> Damit entfallen alle Fehler, die man sich (oder anderen, weniger> begabten Mitstreitern) einbrockt, indem man sich auf eine bestimmte> Suchreihenfolge der Operatoren verlässt.
Das 'Problem' an dieser Stelle ist, dass es für
b = a + 10;
eine definierte Operation gibt, wenn du den op+ als Member machst.
Genau darum geht es, dass es für einen Programmierer, der die Internals
nicht kennt, unlogisch und nicht einsichtig ist, warum
b = a + 10;
kompiliert und
b = 10 + a;
nicht compiliert!
Das ist die eigentliche Motivation, den op+ als globalen Operator zu
implementieren.
Man möchte in beiden Fällen identisches Verhalten haben, denn genau das
ist es, was jeder erwarten wird. Je weniger derartige Überraschungen,
desto besser.
Edit:
Wenn du das hier erzwingen willst
b = FRACTION3(10) + a;
dann muss hier gedreht werden
1
explicitFRACTION3(int);//ÜBERLADEN UND VERKETTEN!!!
Den op+ als Member-Op auszuführen, ist für diese Absicht der falsche Weg
(bzw. es spielt dann keine Rolle mehr)