virtuelle Operatorfunktion


  • Mod

    @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 zu a.operator+=(b).

    Welches Objekt ist bei a.operator+=(b) ausschlaggebend dafür welche Funktion aufgerufen wird, a oder b?



  • 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 oder Matrix *= Skalar. Sogar beim "echten Rechnen", wo + auch "addieren" und * multiplizieren bedeutet, will man das also erlauben 🙂

    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 (man will ja hier die virtuell-Eigenschaft für die Variable mt des eigenen Typs haben - ob ostream.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?


  • Mod

    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.


  • Mod

    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?


  • Mod

    @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 Fehlermeldung

    Eigentlich 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).


  • Mod

    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, wo b1 wie ein Base aussieht, und in Base gibt es nur das eine foo(Base&), das aber in Derived nicht überschrieben wurde. Vergleiche mit dem Fall b1.bar(d2), wo bar(Base&) in Derived überschrieben ist.


  • Mod

    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.


Log in to reply