virtuelle Operatorfunktion
-
@Jockelx sagte in virtuelle Operatorfunktion:
@wob sagte in virtuelle Operatorfunktion:
Ich würde vorschlagen, dann lieber die Operatoren NICHT virtual zu machen. Stattdessen, wenn man das braucht, im Operator einfach die virtuelle Funktion aufrufen
Ich würde überhaupt nichts virtuell machen. += sollte nur für 2 genau gleiche Typen existieren.
Also ich habe nichts dagegen, dass man an einen String einen char heran addieren kann.
-
@C-Sepp sagte in virtuelle Operatorfunktion:
Antwort a ist laut Buch richtig. Also ist das erste Argument entscheidend für den Aufruf der Operatorfunktion?
@SeppJ hat doch schon geschrieben:
Überladenen Operatoren haben keinerlei Sonderregeln,
a += b
ist äquivalent zua.operator+=(b)
.Welches Objekt ist bei
a.operator+=(b)
ausschlaggebend dafür welche Funktion aufgerufen wird,a
oderb
?
-
Das sollte a sein.
-
@SeppJ sagte in virtuelle Operatorfunktion:
Ich würde überhaupt nichts virtuell machen. += sollte nur für 2 genau gleiche Typen existieren.
Also ich habe nichts dagegen, dass man an einen String einen char heran addieren kann.
Und ich denke da an
Vektor += Skalar
oderMatrix *= Skalar
. Sogar beim "echten Rechnen", wo+
auch "addieren" und*
multiplizieren bedeutet, will man das also erlaubenBei meinem ursprünglichen Beitrag dachte ich auch an den
ostream& operator<<(ostream& os, const MeinTypBasisKlasse& mt)
, den ich öfter mal mitreturn mt.virtual_print_to_stream_function(os);
implementiert habe (man will ja hier die virtuell-Eigenschaft für die Variablemt
des eigenen Typs haben - obostream.operator<<
virtuell ist oder nicht, ist mir erstmal wumpe).
-
@SeppJ sagte in virtuelle Operatorfunktion:
Also ich habe nichts dagegen, dass man an einen String einen char heran addieren kann.
@wob sagte in virtuelle Operatorfunktion:
Und ich denke da an Vektor += Skalar oder Matrix *= Skalar. Sogar beim "echten Rechnen", wo + auch "addieren" und * multiplizieren bedeutet, will man das also erlauben
Ja, ich habe mich blöd/falsch ausgedrückt, aber ich dachte im Kontext wäre klar, was ich meine. Eure Beispiele sind ja alle nicht mit Hilfe eines virtuellen Operators definiert worden.
-
@wob sagte in virtuelle Operatorfunktion:
Bei meinem ursprünglichen Beitrag dachte ich auch an den ostream& operator<<(ostream& os, const MeinTypBasisKlasse& mt), den ich öfter mal mit return mt.virtual_print_to_stream_function(os); implementiert habe
Das ist was anderes, da ist ja nur eine Klasse beteiligt. Der virtuelle operator muss aber irgendwie etwas sinnvolles definieren, für alle Kombinationen irgendwelcher Ableitungen. Und das geht dann auch nur mit gecaste.
-
Dementsprechend wird dann also, wenn zwei Objekte vom Typ Derived übergeben werden, die Operatorfunktion in Derived aufgerufen und einmal die Derived übergeben und von Derivd in Base konvertiert?
-
Du hast doch überall Referenzen. Hier wird nirgendwo ein Objekt konvertiert, gespliced, oder sonstwie umgewandelt.
-
Also In meinen Buch gibt es ein kleines Kapitel: "Konvertierung in Referenzen auf Basisklassen".
Darin steht: Beim Arbeiten mit Referenzen ist eine analoge Situation gegeben. Eine Referenz vom Typ "Referenz auf Basisklasse" kann auf ein Objekt einer abgeleiteten Klasse verweisen. Die Referenz stellt dann nur den Basisnateil dar.
-
Das ist korrekt.
-
@C-Sepp
Der Typ wird doch nur benutzt, um die richtig Überladung für den Funktionsaufruf zu finden.
-
Und warum wird dann in dem Fall, dass zwei Derived-Objekte übergeben werden und die "passendste" Überladung ausgewählt wird, nichts konvertiert?
-
@C-Sepp sagte in virtuelle Operatorfunktion:
Und warum wird dann in dem Fall, dass zwei Derived-Objekte übergeben werden und die "passendste" Überladung ausgewählt wird, nichts konvertiert?
@SeppJ sagte in virtuelle Operatorfunktion:
Du hast doch überall Referenzen. Hier wird nirgendwo ein Objekt konvertiert, gespliced, oder sonstwie umgewandelt.
-
@C-Sepp sagte in virtuelle Operatorfunktion:
Die Referenz stellt dann nur den Basisnateil dar.
Ich finde diesen Satz verwirrend at best. Eine Referenz referenziert immer ein Objekt, ich weiß nicht, was hier mit "stellt dann nur den Basisanteil dar." gemeint ist. Die Referenz zeigt auf das ganze, echte, möglicherweise abgeleitete Objekt.
Stelle dir eine Referenz wie ein Pointer vor, der immer initialisiert werden muss und dem du nie
nullptr
zuweisen kannst - und bei dem der->
sozusagen fest eingebaut ist.
-
@wob sagte in virtuelle Operatorfunktion:
Eine Referenz referenziert immer ein Objekt,
Leider nein, denn das Objekt auf dem die Referenz verweist kann bereits zerstört sein.
-
"Die Referenz stellt dann den Basisanteil dar"
Ja genau dieser Satz war es, der verwirrt hat. Das Kapitel "Kovertierung in Referenzen auf Basisklassen" folgt auch ausgerechnet den Kapitel "Konvertierung in Basisklassenzeiger".
-
Es gibt noch eine weitere Aufgabe:
"Angenommen in einer Basisklasse Base ist eine virtuelle Operatorfunktion für die Zuweisung definiert. Die von Base abgeleitete Klasse Derived besitzt keine selbst defnierte Operatorfunktion für die Zuweisung. Außerdem ist eine globale
Funktion cpy() wie folgt definiert:void cpy(Base& b1, const Base& b2) { b1=b2; }
Welche Version des Zuweisungsoperators wird in der Funktion cpy() aufgerufen, wenn als erstes Argument ein Objekt der Klasse Derived und als zweites Argument ein Objekt der Klasse Base übergeben wird?
a.): Die Standardzuweisung der Klasse Derived
b.): Die in der Basisklasse Base definierte virtuelle Operatorfunktion
c.): Der Compiler liefert beim Aufruf eine FehlermeldungEigentlich hätte ich auf a getippt, da das erste Argument der Standardzuweisung der Klasse Derived ja zu den übergebenen Arguement Derived passt. Richtig ist allerdings b. Womit lässt sich das jetzt erklären?
Operatorfunktion passt, nicht die
-
Der vom Compiler erzeugte operator hat ein 'Derived' als Parameter, nicht Base.
Dieser Operator überschreibt den aus der Basisklasse also auch nicht und b1 hat nichts besserers als eben den operator der Basisklasse (zwar virtuell, aber nicht überschrieben).
-
Das ist eine Frage der passenden Parameter, siehe Jockelx' Erklärung. Zum besseren Verständnis studiere dies genau:
#include <iostream> using namespace std; struct Base { virtual void foo(Base &base){cout << "Base::foo\n";} virtual void bar(Base &base){cout << "Base::bar\n";} }; struct Derived: public Base { virtual void foo(Derived &derived){cout << "Derived::foo\n";} virtual void bar(Base &base){cout << "Derived::bar\n";} }; int main() { Derived d1, d2; Base &b1 = d1, &b2=d2; b1.foo(b2); // Base foo. Dies ist dein Fall b1.foo(d2); // Base foo. Dies ist wahrscheinlich unerwartet // d1.foo(b2); // Geht nicht. d1.foo muss das foo in Derived sein, aber b2 ist kein Derived d1.foo(d2); // Derived foo b1.bar(b2); // Derived bar b1.bar(d2); // Derived bar d1.bar(b2); // Derived bar d1.bar(d2); // Derived bar }
Der als 'unerwartet' kommentierte Fall hat es natürlich in sich. Hier kommt meine Bemerkung von oben zu tragen, dass das zwar geregelt ist, aber nicht unbedingt intuitiv. Möchte sich jemand opfern, das zu genau erklären? Das wird gewiss ein längerer Text. Die Kurzfassung ist, dass hier
foo
nicht nur virtual ist, sondern auch überladen. Und Überladungen werden zur Compilezeit ausgewertet, wob1
wie einBase
aussieht, und in Base gibt es nur das einefoo(Base&)
, das aber in Derived nicht überschrieben wurde. Vergleiche mit dem Fallb1.bar(d2)
, wobar(Base&)
inDerived
überschrieben ist.
-
PS: Als Ergänzung der Fall, wo das passiert, was man intuitiv erwartet, weil die überladenen Funktionen auch jeweils virtuell überschrieben sind:
#include <iostream> using namespace std; struct Derived; struct Base { virtual void foo(Base &base){cout << "Base::foo Base\n";} virtual void foo(Derived &derived){cout << "Base::foo Derived\n";} }; struct Derived: public Base { virtual void foo(Base &base){cout << "Derived::foo Base\n";} virtual void foo(Derived &derived){cout << "Derived::foo Derived\n";} }; int main() { Derived d1, d2; Base &b1 = d1, &b2=d2, b3; b1.foo(b2); // Derived::foo Base b1.foo(d2); // Derived::foo Derived d1.foo(b2); // Derived::foo Base d1.foo(d2); // Derived::foo Derived b3.foo(b2); // Base::foo Base b3.foo(d2); // Base::foo Derived // Kann keinen Derived& auf b3 haben }
100 Internetpunkt für denjenigen, der jetzt auch noch Templates mit in die Erklärung aufnimmt.