Hallo,
ich versuche zur Zeit meine C++ Kenntnisse zu verbessern. In dem
Beispiel wird der Operator "+" überladen. Wenn ich nun in der main()
statische Objekte definiere funktioniert alles bestens. Versuche ich die
Objekte dynamisch zu erzeugen und verwende den "+" Operator bekomme ich
die Fehlermeldung:
invalid operands of types `Complex*' and `Complex*' to binary
`operator+'
Es kommt mir so vor als nimmt der Compiler den überladenen Operator gar
nicht wahr. Wo ist mein Denkfehler?
class Complex {
// Diese beiden Operatorfunktionen muessen auf die
// privaten Member "m_re" und "m_im" zugreifen können!
friend Complex operator+ (const Complex &, const Complex &);
friend Complex operator- (const Complex &, const Complex &);
public:
Complex () {}
Complex (double re, double im) : m_re(re), m_im(im) {}
private:
double m_re;
double m_im;
};
Complex operator+ (const Complex &c1, const Complex &c2)
{
Complex result;
result.m_re = c1.m_re + c2.m_re;
result.m_im = c1.m_im + c2.m_im;
return result;
}
Complex operator- (const Complex &c1, const Complex &c2)
{
Complex result;
result.m_re = c1.m_re - c2.m_re;
result.m_im = c1.m_im - c2.m_im;
return result;
}
int main()
{
/*Complex Complex1(1,3); keine Probleme
Complex Complex2(2,4); bei statisch
Complex Complex3; erzeugten Objekten
Complex3 = Complex1 + Complex2;*/
Complex *Complex1, *Complex2, *Complex3; // Compilerfehler
Complex1 = new Complex(1,3); // bei dynamischer
Complex2 = new Complex(2,4); // Erzeugung
Complex3 = Complex1 + Complex2;
return 0;
}
natürlich geht das nicht. Denn was new dir zurückgibt, ist die Adresse
an der ein konstruirtes Objekt liegt.
*Complex3 = *Complex1 + *Complex2;
Das müsste gehen.
Optional kann man auch noch so überladen
Complex operator+ (const Complex * c1, const Complex * c2) {
Complex tmp;
return tmp;
}
Hat auch etwas overhead, wie der andere Fall auch.
>> natürlich geht das nicht. Denn was new dir zurückgibt, ist die Adresse> an der ein konstruirtes Objekt liegt.>> *Complex3 = *Complex1 + *Complex2;>> Das müsste gehen.
Nein, das wird auch nicht gehen.
Grund: Complex3 zeigt auf nichts gültiges.
So müsste es gehen:
Complex *Complex1, *Complex2, *Complex3; // Compilerfehler
Complex1 = new Complex(1,3); // bei dynamischer
Complex2 = new Complex(2,4); // Erzeugung
Complex3 = new Complex(0,0);
Complex3 = Complex1 + Complex2;
>> Optional kann man auch noch so überladen>> Complex operator+ (const Complex * c1, const Complex * c2) {> Complex tmp;> return tmp;> }>> Hat auch etwas overhead, wie der andere Fall auch.
Das ist in (fast) jedem Fall besser.
Nebenbei: Die Namen Complex1 etc. sind extrem ungünstig, weil sie
suggerieren, sie würden etwas vom Typ Complex repräsentieren.
Tatsächlich sind es Zeiger auf Complex, sollten also etwa
p_Complex1 etc. heißen.
und noch eine Anmerkung:
die ursprüngliche auskommentierte Version wird im Kommentar als
"statisch" bezeichnet; das ist falsch.
Statische Variablen sind entweder globale Variablen oder lokale,
wenn sie zusätzlich mit static deklariert sind.
Lokale Variablen ohne static (also die auskommentierten)
werden beim Eintritt in ihren Geltungsbereich (bei der
vorhergehenden öffnenden geschweiften Klammer) automatisch
erzeugt und beim Verlassen des Blocks ebenso automatisch wieder
aufgelöst (weshalb sie auch automatische Variablen heißen).
Klaus Wachtler schrieb:
>> Complex operator+ (const Complex * c1, const Complex * c2) {>> Complex tmp;>> return tmp;>> }>>>> Hat auch etwas overhead, wie der andere Fall auch.>> Das ist in (fast) jedem Fall besser.
Bei so etwas hab ich zwiespältige Gefühle (einfach bei Operator
Überladung schon zuviel erlebt).
Ich denke bei der Überladung von Operatoren sollte man sich an die
simple Regel halten: Wenn du in Rom bist, machs wie die Römer
Was meint man damit:
Der Versuch 2 Pointer zu addieren wird normalerweise mit einem Error
quittiert, weil das von der Sprachdefinition her (zu Recht) verboten
ist. Für meine eigenen Klassen würde ich daher ebenfalls so eine
Operation nicht zulassen wollen.
'Machs wie die Römer' bedeutet: denk darüber nach, ob es für int
dieselbe Operation gibt, bzw. wie verhält sich diese, und modellier
deine eigenen Operatoren nach diesem Vorbild.
Letztenendes gewinnt man dadurch auch nichts, ausser ein bischen Komfort
an der Aufrufstelle, an der man sich den Dereferenzier-* spart. Im
Programm selbst bringt das nichts. Das Programm selbst hat deswegen auch
nicht weniger Arbeit. Ganz im Gegenteil: Referenzen sind in der
Sprachdefinition so gemacht, dass sie dem Compiler einige Optimierungen
ermöglichen, so dass der normale Operator nicht wirklich grossartigen
Overhed erzeugt.
Wenn man einmal weiterdenkt, dann hat man mit so einem Operator genau
einen speziellen Fall abgedeckt. Was ist mit Mischformen?
Complex a;
Complex* b;
c = a + b;
c = b + a;
Für all diese Fälle müsste man eigene Operatoren vorsehen.
Was ist, wenn ich die eigentlichen Objekte in einem Smart-Pointer habe?
Was ist, wenn die Objekte in einem Container stecken und ich einen
Iterator darauf habe?
Baut man aber zuviele Operatoren, dann läuft man wiederrum Gefahr, dass
man dem Compiler eine Menge Wege eröffnet, wie er einen an sich
ungültigen Ausdruck doch noch compilierbar macht.
Ich finde: Operatoren sollten als Argumente auf Objektebene mit
Referenzen arbeiten. So wie die eingebauten Operatoren auch auf zb int
oder double arbeiten und nicht auf int* oder double*.
Eine Variation des ganzen, die man häufiger sieht, ist ein
'Copy-Konstruktor', der als Argument keine const Referenz akzeptiert,
sondern einen const Pointer. Find ich auch nicht so gut. Aus nahezu den
gleichen Gründen.
Karl heinz Buchegger schrieb:
> Klaus Wachtler schrieb:>>>> Complex operator+ (const Complex * c1, const Complex * c2) {>>> Complex tmp;>>> return tmp;>>> }>>>>>> Hat auch etwas overhead, wie der andere Fall auch.>>>> Das ist in (fast) jedem Fall besser.>> Bei so etwas hab ich zwiespältige Gefühle (einfach bei Operator> Überladung schon zuviel erlebt).> ...
Sorry, ich nehme alles zurück und behaupte das Gegenteil - ich hatte
den Text nicht richtig gelesen.
Das Überladen des Operators für const Complex * ist natürlich Quatsch
und nicht mal erlaubt (error: ‘Complex operator+(const Complex*,
const Complex*)’ must have an argument of class or enumerated type),
würde es der Compiler zulassen wäre es immer noch kriminell. da hast
du vollkommen recht.
Ich sehe es genauso, nämlich daß überladene Operatoren nur genau
das machen sollten, was man an der Aufrufstelle erwarten würde.
Eine Addition von Zeigern klammheimlich durch eine Addition
komplexer Zahlen zu überschreiben, wäre vollkommen abstrus.
Was ich meinte, daß es regelmäßig besser wäre, ist das was Peter
dann schrieb: die Operatoren in die Klasse mit aufnehmen (wobei ich
sogar auch die Definition immer in die Klasse schreiben würde, aber
das ist vielleicht Geschmackssache; zumindest inline sollten sie sein).
Es ist eh nicht sehr sinnvoll bei C++ noch mit Zeiger rumzuhantieren,
dann bei C++ gehören auch Exception dazu. Wenn du jezt Object mit new
anlegst dann musst du dich selber um das Delete kümmern.
man kann es mit hilfe von AutoPtr lösen oder immer sauber einen Catch
block verwenden wo alle objecte gelöscht werden.
Aber man sollte davon ausgehen das bei jeder Anweisung ein Exception
erfolgen kann.
var x = new var;
machwas( x );
delete x;
und schon hast du ein Speicherloch wenn in machwas eine Exception
geworfen wird.
matrix:/pool/c++/overload# g++ -o main main.cpp -Wall
2
matrix:/pool/c++/overload# ./main
3
id=0 => (1,1)
4
id=1 => (2,0)
5
id=3 => (3,1)
wenn man /**/ weglässt gibt es tatsächlich diesen Fehler.
Ich bin schon länger aus der C++ Programmierung raus, dennoch
soweit ich mich richtig erinnere ging das mit gcc3.*
Würde mich interessieren wieso es verboten sein sollte.
Weil per Sprachdefinition mindestens einer der Operanden ein
Klassenobjekt oder eine Referenz darauf sein muss.
Es wäre ausserdem alles andere als stimmig, wenn man die Addition zweier
Pointer per Overloading definieren könnte, die Subtraktion aber nicht.
Denn die ist bereits über das C Erbe definiert und somit tabu.
> Das Überladen des Operators für const Complex * ist natürlich Quatsch
2
> und nicht mal erlaubt (error: ‘Complex operator+(const Complex*,
3
> const Complex*)’ must have an argument of class or enumerated type),
4
> würde es der Compiler zulassen wäre es immer noch kriminell.
5
>
>> Warum? Welcher Compiler?
Die Meldung kam vom gcc 4.3.2.
Ich kann mir kaum vorstellen, daß ältere Versionen das zulassen,
aber weiß es natürlich nicht.
Klaus Wachtler schrieb:
> Was ich meinte, daß es regelmäßig besser wäre, ist das was Peter> dann schrieb: die Operatoren in die Klasse mit aufnehmen (wobei ich> sogar auch die Definition immer in die Klasse schreiben würde, aber> das ist vielleicht Geschmackssache; zumindest inline sollten sie sein).
Vorsicht.
Das kommt auf den Operator an.
Bei den Operatoren für +, i, * und / ist diese Lösung meistens nicht
besser (von Sonderfällen abgesehen).
+, -, *, / und % werden am besten als freistehende Operatoren gelöst. So
gesehen hatte das der OP schon richtig.
Was ist das Problem?
Machst du diese Operatoren so wie Peter das vorgeschlagen hat, dann hast
du möglicherweise (kommt auf die Konstruktoren an) eine Asymetrie in der
Verwendung.
a=b+2;// das compiliert. Aus der 2 wird mit dem Konstruktor der
21
// den imaginär Teil default auf 0 hat, ein Complex geformt,
22
// und dann b.op+ aufgerufen
23
24
a=2+b;// das copmiliert nicht. Es gibt keine Möglichkeit wie
25
// der Compiler das umformen könnte um die Memberfunktion
26
// op+ aufrufen zu können. Es existiert ganz einfach kein
27
// Complex Objekt, für das das gehen könnte.
28
}
Gerade in obigem Beispiel ist aber nicht ersichtlich, wieso b + 2 schon,
2 + b aber nicht compilierbar sein sollte.
Und einen Konstruktor mit dem man auf einfache Weise einen Complex aus
einer Zahl erzeugen können soll, wird man wohl so gut wie immer in so
eine Klasse mit aufnehmen.
Wohingegen
compiliert in beiden Fällen und wird auch in beiden Fällen richtig
umgesetzt. Ob man den Zugang zu den Member dann über friend
Deklarationen oder über (wharscheinlich sowieso vorhandenen) Gettern
macht, ist Geschmackssache.
In diesem konkreten Fall, spielt es auch nicht so sehr die Rolle, ob man
die Addition bereits bei der Initialisierung des Rückgabeobjektes macht
oder nicht, trotzdem sollte man sich das gleich angewöhnen. Bei String
Objekten zb spielt es eine Rolle: Es ist sinnlos sich zunächst ein
leeres String Objekt zu erzeugen und dann diesem leeren String Objekt
einen String zuzuweisen. Da kann man auch gleich das neue String Objekt
mit dem richtig String erzeugen zu lassen. Ist schon interessant (hab
ich hier in der Firma auch): Auf der einen Seite wird viel Aufwand
getrieben um Programme möglichst optimal zu schreiben und die
einfachsten Dinge um Zeit einzusparen werden regelmäsig vernachlässigt
:-)
Zusammen mit der ominösen 'Named return value optimization (*)' wird der
Compiler höchst wahrscheinlich aus diesem Code das Optimum herausholen.
In den Operatoren ist es wahrscheinlich, dass dort noch nicht mal
temporäre Zwischenobjekte entstehen.
(*) Das ist die einzige Optimierung die im C++ Standard konkret
angesprochen wird und auch erlaubt wird.
Konkret geht es darum, dass am Beispiel des op+ von da oben, der
Compiler das Zwischenobjekt für den Return nicht konstruieren muss,
sondern das Ergebnis auch gleich im Zielobjekt der Zuweisung
konstruieren darf.
Anstelle von (ich verwende jetzt eine etwas verkürzte Schreibweise)
Comp op+( const Comp & a, const Comp & b )
....
Comp c( a + b );
(also: a + b aufrufen, temporäres Objekt benutzen um damit c per Copy
Construktor zu initialisieren)
darf der Compiler das ganze auch so (intern) implementieren
Comp op+( const Comp & a, const Comp & b, Comp & result )
// aus Copm c( a + b ) wird
Comp c; // wobei c als uninitialisiertes Objekt erzeugt wird
op+ ( a, b, c );
Diese Zusicherung der Legalität im Standard ist wichtig, weil es hier
ein konzeptionelles Problem gibt: Konzeptionell wird aus dem Operator
ein Objekt zurückgegeben mit dem dann das eigentliche Objekt per Copy
Constructor initialisiert wird. Im unteren Fall wird aber der Copy
Construktor nie aufgerufen! Das Ergebnis wird direkt in der
Ergebnisvariablen konstruiert.
Daher ist es auch unklug, wenn ein Copy Construktor mehr macht als ein
Objekt zu konstruieren. Der Compiler darf Aufrufe zum CCtor
wegoptimieren, wenn er will!
zb. beim inlinen tauchen solche Fälle regelmässig auf. Der C++ Standard
erlaubt explizit, den Returnvalue bereits in der Zielvariablen zu
konstruieren und einen CCtor unter den Tisch fallen zu lassen