PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : c++ Überladen von Operator


Supa
2006-01-08, 00:55:30
ich hab eine Klasse die die privaten Felder
int nenner
in zaehler
hat

desweiteren:


RationaleZahl& RationaleZahl::operator* (const RationaleZahl& x)
{
zaehler = zaehler*x.zaehler;
nenner = nenner*x.nenner;
return *this;
};


ich habe jetzt zb 3 Objekte a b c, alle gefüllt,
wenn ich jetzt sage a=b*c
dann ist in a das ergebniss aber in b auch, nur c ist noch wie vorher.
Wie bekomme ich das hin das b auch seinen inhalt behält.

Btw: was bedeutet das return *this, genau?

Expandable
2006-01-08, 01:19:25
Dein überladener Operator ist eine Elementfunktion von RationaleZahl. zahler = ändert also zwangsläufig Dein b. (b * c ruft auf: b::operator*(c)).

Wenn Dein b unverändert bleiben soll, ist es wohl am besten, Du definierst eine Hilfsfunktion (oder Du machst es so, wie im nächsten Post beschrieben. Die Frage ist halt, was Du erreichen willst. Ist es richtig, dass sich Dein b ändert? In dem Fall wohl kaum. Könnte ja aber in manchen Fällen tatsächlich zutreffen). Also z.B. (ungetestet, aber sollte gehen):


RationaleZahl operator*(const RationaleZahl &lhs, const RationaleZahl &rhs)
{
RationaleZahl tmp(); // Temporäre Rationalezahl, wo das Ergebnis gespeichert und zurückgeliefert wird. Ein gültiger Default-Konstruktor wird angenommen. Der Wert, der nachher in tmp steht, wird dann a zugewiesen und nach der Zuweisung wieder gelöscht.
tmp.zaehler = lhs.zahler * rhs.zaehler; // Zählermultiplikation
tmp.nenner = lhs.nenner * rhs.nenner; // Nennermultiplikation
return tmp; // tmp zurückgeben, damit es an a zugewiesen werden kann. Hier darfst Du KEINE Referenz zurückgeben, da eine Referenz auf ein lokales Objekt (= tmp) nach dem Verlassen der Funktion nicht definiert ist (der Compiler sollte meckern)
}

Nun bleibt Dein b unverändert, denn b * c ruft nun auf operator*(b, c), wobei b und c als konstante Referenz übergeben werden und somit nicht mehr veränderbar sind.

Zu *this: In jeder Elementfunktion einer Klasse kannst Du implizit mit *this auf die Elementvariablen zugreifen. Du kannst in einer Elementfunktion also schreiben

void RationaleZahl::f()
{
nenner = 22;
(*this).nenner = 22;
this->nenner = 22;
}

Das ist semantisch alles das gleiche. Elementfunktionen können einfach auf Elementvariablen zugreifen, wie hier nenner = 22 (bedeutet b.nenner = 22, falls b der Name deines Objektes ist - beachte jedoch, dass der Name b in der Funktion selbst nicht verfügbar ist!).

this ist ein Zeiger, der auf das aktuelle Objekt zeigt, und implizit immer vorhanden ist (innerhalb von nicht-statischen Elementfunktionen). Du kannst also mit (*this).nenner auf die Nennervariable des aktuellen Objektes zugreifen, oder, bequemer, mit this->nenner = 22; Wobei der Unterschied zwischen (*this). und this-> nur in der Schreibweise liegt (letztere ist bequemer und übersichtlicher). Wenn Du nun return *this schreibst, gibst Du den Inhalt, auf den der Zeiger this zeigt, also das aktuelle Objekt, zurück.

Ein paar weitere Gedankengänge: Da Deine obige Funktion eine Referenz zurückgibt, ersparst Du Dir sogar Kopieroperationen (schneller). Du könntest auch genau so gut direkt this (ohne *) zurückgeben, also die Adresse auf das Objekt, und somit einen Zeiger. Hätte prinzipiell einen ähnlichen Effekt wie eine Referenz, nur sind Referenzen in solchen Fällen wesentlich schöner und einfach zu handhaben (und wenn Du return this machst und einen Pointer zurückgibst, sollte sowas wie a = b + c + d eigentlich nicht funktionieren ;))

Trap
2006-01-08, 01:20:14
Richtig ist:
RationaleZahl RationaleZahl::operator* (const RationaleZahl& x)
{
RationaleZahl ergebnis;
ergebnis.zaehler = zaehler*x.zaehler;
ergebnis.nenner = nenner*x.nenner;
return ergebnis;
};

Warum das richtig ist und was an deinem falsch ist können sicher noch andere erklären, ich geh jetzt ins Bett.

Supa
2006-01-08, 05:35:46
Richtig ist:
RationaleZahl RationaleZahl::operator* (const RationaleZahl& x)
{
RationaleZahl ergebnis;
ergebnis.zaehler = zaehler*x.zaehler;
ergebnis.nenner = nenner*x.nenner;
return ergebnis;
};

Warum das richtig ist und was an deinem falsch ist können sicher noch andere erklären, ich geh jetzt ins Bett.


Habs so gemacht, klappt , aber lasst mich raten, so klappt das verknüpfen von zb a+b*c/d nicht? weil da kommt bei mir nur absoluter müll raus.

Expandable
2006-01-08, 11:24:10
Sollte eigentlich funktionieren. Wie sehen denn RationaleZahl::operator+ und RationaleZahl::operator/ aus?

Marscel
2006-01-08, 12:29:50
Warum das richtig ist und was an deinem falsch ist können sicher noch andere erklären, ich geh jetzt ins Bett.

Bei seinem Code verändert der Operator* die Werte von Objekt b direkt.

Richtig ist eben, das b seine eigenen Werte mit denen von c mulitpliziert und einer temporären Instanz zuweist, die dann a zugewiesen wird. So bleibt Objekt b unberührt.

Und zu deiner anderen Frage, operator+ und operator/ müssen dementsprechend auch überladen werden.

Trap
2006-01-08, 13:43:40
Ein paar weitere Gedankengänge: Da Deine obige Funktion eine Referenz zurückgibt, ersparst Du Dir sogar Kopieroperationen (schneller).
Schneller, aber es macht nichtmehr das was man von * erwarten würde. Insbesondere gibt es keinen Weg mit Referenzen als Rückgabewert sowas richtig auszurechnen:
x = a*b+a*c+a*d;

Noch ein Problem mit Referenzen: a*b=c; ist erlaubt, was nicht so schön ist.

Gast
2006-01-08, 20:16:25
Operator * (binär, nicht der Dereferenzierungsoperator) sollte man nicht als Memberfunktion implementieren!!

Normalerweise sieht das etwa so aus:

class Rational
{
int zaehler, nenner;

/* ... */

public:

/* ... */
Rational(Rational const& num) { /*...*/ }
Rational& multiply(Rational const& num) {
zaehler += num.zaehler;
nenner += num.nenner;
return *this;
}
}

Rational operator*(Rational const& lhs, Rational const& rhs) {
Rational tmp(lhs);
return tmp.multiply(rhs);
}

Coda
2006-01-08, 20:46:14
Operator * (binär, nicht der Dereferenzierungsoperator) sollte man nicht als Memberfunktion implementieren!!Wieso eigentlich nicht?

Gast
2006-01-08, 21:09:00
Darum: (Das macht in seinem Beispiel kein Unterschied wenn er es mit 2 Rationals macht, aber wenn er nochmal für double, int etc überladt. Da würde eventuell auch ein Template Operator Sinn machen wenn er die Konstruktoren für die Typen hat)

struct IntValue
{
IntValue(int a) : val(a) { }
IntValue operator+(int a) { return IntValue(val + a); }
int val;
};

int main()
{
IntValue intval(1);
IntValue neu_a(intval + 5); // geht
IntValue neu_b(5 + intval); // PUFF geht nicht
}

Gast
2006-01-08, 21:11:19
Achja @Trap: Wenn man keine Ahnung hat, ... Musste nun gesagt werden weil du deine Lösung als unfehlbar richtig darstellst obwohl sie nicht richtig ist.

Trap
2006-01-08, 21:52:51
Ich hab eine richtige Implementierung vom operator* als Memberfunktion geschrieben, dass man den operator* üblicherweise nicht als Memberfunktion schreibt ist ein anderes Thema.

Gast
2006-01-08, 22:41:15
Syntakisch richtig, mehr aber auch nicht! Es macht auf jeden Fall auch so immer noch einen gravierenden Unterschied! Schau dir folgendes Beispiel an:

struct IntValue
{
IntValue(int a)
: val(a)
{ }

IntValue operator+(IntValue const& a)
{
return IntValue(val + a.val);
}

int val;
};

int main(int argc, char *argv[])
{
IntValue a(5);
IntValue b(5 + a);
}

Das geht nun nicht! Würde man den operator+ als globale Funktion implementieren würde die 5 implizit zu einem IntValue gecastet werden (Aufruf des Konstruktors). Geht bei der Memberfunktion nicht!

Trap
2006-01-08, 23:39:56
Das Probelm wird offensichtlicher wenn man es so schreibt:
IntValue a(5);
a = 5 + a;
Ja, meine Lösung compiliert nicht für alle Ausdrücke die man gern hätte, wenn sie kompiliert ist sie aber richtig. Ich würd sagen sie ist richtig, aber nicht vollständig.

Mal ne interessantere Frage:
Wenn eine Klasse X einen binären operator+(const X&) hat und ein globaler operator+(const X&,const X&) existiert was wird aufgerufen und warum?
X a;
a = a + a;

Gast
2006-01-09, 06:36:11
Ich bin mir jetzt ehrlich gesagt nicht ganz sicher, aber afaik hat die Memberfunktion Vorrang, selbst wenn sie ein Template ist. *Im-Standard-rumblätter*
13.3.1.2 sollte es sein.

Gast
2007-12-13, 09:58:11
Wenn man für ein Struct den + operator überladen hat, ist es dann korrekt, richtig, sinnvoll den += Operator so zu implementieren?

Stru operator+( const Stru& op ) const;
Stru operator+=( Stru& op )
{
(*this) = (*this) + op;
return *this;
};
thx

Neomi
2007-12-13, 12:09:50
Wenn man für ein Struct den + operator überladen hat, ist es dann korrekt, richtig, sinnvoll den += Operator so zu implementieren?

Nein, nicht ganz. Der Rückgabewert von += sollte (genau wie bei allen anderen Zuweisungsoperatoren) eine Referenz sein. Der Parameter sollte konstant sein. Dann wäre es zwar "korrekt", aber sinnvoll ist von Fall zu Fall eher eine direkte Verarbeitung ohne temporäres Objekt. Je nach Art der Struktur kann der Compiler den Zusatzaufwand für ein temporäres Objekt rausoptimiern, aber nicht in Debug-Builds und auch sonst nicht immer.

Gast
2007-12-13, 13:27:26
danke
Stru& operator+=( const Stru& op )
{
(*this) = (*this) + op;
return *this;
}; Also so wäre es richtiger, aber was meinst Du mit "direkte Verarbeitung ohne temporäres Objekt".

Wenn ich sowas zurückgebe, erhalte ich doch eine kaputte Referenz?
Stru& operator+=( const Stru& op )
{
return (*this) + op;
};

malte.c
2007-12-13, 15:13:41
Verwendet doch einfach http://www.boost.org/libs/utility/operators.htm: Damit schreibt man z.B. eine Variante eines Operators (z.B. +=) und bekommt die andere gratis dazu (+).

Neomi
2007-12-13, 16:55:38
Also so wäre es richtiger, aber was meinst Du mit "direkte Verarbeitung ohne temporäres Objekt".

Wenn ich sowas zurückgebe, erhalte ich doch eine kaputte Referenz?
Stru& operator+=( const Stru& op )
{
return (*this) + op;
};

Mit direkter Verarbeitung meine ich eine Verarbeitung ohne Umwege über bereits definierte Sachen. Am Beispiel einer konkreten kleinen Struktur sieht das so aus:

struct float3
{
float x, y, z;

float3(float x, float y, float z) : x(x), y(y), z(z) {}
};

float3 operator + (const float3 &v1, const float3 &v2)
{
return(float3(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z));
}

// Variante 1: über bereits definierte Sachen

float3& operator += (const float3 &v)
{
*this = *this + v;
return(*this);
}

// Variante 2: direkte Verarbeitung

float3& operator += (const float3 &v)
{
x += v.x;
y += v.y;
z += v.z;
return(*this);
}

Variante 2 wird aufgerfen, macht was sie machen soll und ist fertig.

Variante 1 ruft erst den definierten Operator + auf (1. Unteraufruf) und konstruiert darin mit einem parametrisierten Konstruktor ein neues Objekt mit dem gewünschten Ergebnis (2. Unteraufruf). Dieses Objekt wird über den Operator = (hier implizit, bei Klassen mit dynamischem Speicherbedarf nötig) zugewiesen, also noch ein Aufruf (3. Unteraufruf). Vom temporären Objekt wird dann noch der Destruktor (hier implizit und außerdem untätig, aber bei Klassen mit dynamischem Speicherbedarf nötig) aufgerufen (4. Unteraufruf).

Die erste Variante sorgt in diesem Minibeispiel für 2 zusätzliche Aufrufe, die vom Compiler rausoptimiert werden können, in Debug-Builds aber drin bleiben und damit den Code vergrößern und die Ausführungszeit verlängern. In anderen Klassen kann es bis zu 4 zusätzliche Aufrufe geben, die im Extremfall nicht rausoptimiert werden können und sogar noch zusätzliche malloc/free bzw. new/delete mit sich bringen, was dann auch im Release-Build die Ausführungszeit anwachsen läßt.

Xmas
2007-12-14, 00:43:09
Das geht nun nicht! Würde man den operator+ als globale Funktion implementieren würde die 5 implizit zu einem IntValue gecastet werden (Aufruf des Konstruktors).
Was allerdings auch gar nicht immer erwünscht ist. Insofern wäre es schon legitim eine Variante als Member und die andere als friend oder global zu implementieren, obwohl es wahrscheinlich besser ist das ganze konsistent zu machen.

Was + und += angeht, so würde ich eher + mittels += implementieren statt andersrum. Oder eben, wie Neomi vorschlägt, direkte Verarbeitung. Meist sind es ja eh nur zwei oder drei Zeilen.