virtuelle Operatorfunktion


  • 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.



  • @wob sagte in virtuelle Operatorfunktion:

    ich weiß nicht, was hier mit "stellt dann nur den Basisanteil dar." gemeint ist.

    Na dass die Referenz eben vom Typ Basisklasse& ist, und sich daher überall wo kein virtual im Spiel ist wie das Basisklassen-Subobjekt verhält.



  • Aber:

    "stellt dann nur den Basisanteil dar."

    kann doch etwas missverständlich sein, denn es zeigt

    @wob sagte in virtuelle Operatorfunktion:

    Die Referenz zeigt auf das ganze, echte, möglicherweise abgeleitete Objekt.

    was man schön zeigen kann, wenn das abgeleitete Objekt eine Variable definiert und in einer überschriebenen Methode darauf zugreift, dann geht das auch mit einer Basisklassenreferenz auf dieses abgeleitete Objekt, obwohl die Basisklasse selbst diese Variable nicht hat/kennt.



  • @Belli sagte in virtuelle Operatorfunktion:

    Aber:

    "stellt dann nur den Basisanteil dar."

    kann doch etwas missverständlich sein, denn es zeigt

    Natürlich ist es schlecht formuliert und kann leicht misverstanden werden. Aber wenn man weiss wie C++ funktioniert, dann ist schon klar was es bedeuten soll.

    @wob sagte in virtuelle Operatorfunktion:

    Die Referenz zeigt auf das ganze, echte, möglicherweise abgeleitete Objekt.

    Das stimmt BTW auch nicht. Die Referenz zeigt auf das Basisklassen-Subobjekt. Das fängt oft an Offset 0 des ganzen Objekts an, aber weit nicht immer.

    was man schön zeigen kann, wenn das abgeleitete Objekt eine Variable definiert und in einer überschriebenen Methode darauf zugreift, dann geht das auch mit einer Basisklassenreferenz auf dieses abgeleitete Objekt, obwohl die Basisklasse selbst diese Variable nicht hat/kennt.

    Naja. Ich weiss worauf du hinaus willst, aber so wie du das formuliert hast, stimmt es nicht.

    Du kannst mit einer Referenz auf die Basisklasse niemals auf eine Membervariable der abgeleiteten Klasse zugreifen. Maximal auf virtuelle Memberfunktionen der abgeleiteten Klasse. Beim Aufruf wird der implizite this Parameter dann in einen Zeiger der abgeleiteten Klasse konvertiert. Und über den kann man in der Memberfunktion dann zugreifen.



  • @Jockelx : Okay...also haben die vom Compler erzeugten Operatorfunktionen in der jeweiligen Klasse
    immer diese Form?:

    class& operator= (const class& obj)
    

    Da diese für unterschiedliche Klassen unterschiedliche Parameter haben, werden sie also nicht überschrieben.

    SeppJ sagte:
    "// d1.foo(b2); // Geht nicht. d1.foo muss das foo in Derived sein, aber b2 ist kein Derived"

    d1.foo ist doch das foo in derived. Eigentlich können einen Parameter vom Typ der abgeleiten Klasse doch keine Parameter vom Typ der Basisklasse zuweisen werden. Das kann es doch im Endeffekt nur sein!?


  • Mod

    @C-Sepp sagte in virtuelle Operatorfunktion:

    @Jockelx : Okay...also haben die vom Compler erzeugten Operatorfunktionen in der jeweiligen Klasse
    immer diese Form?:

    class& operator= (const class& obj)
    

    Klugscheißermodus Sonderfall: Normal ist zwar X& X::operator=(const X&) (also das, was du schreibst), aber wenn nötig kann es auch zu X& X::operator=(X&) werden.

    SeppJ sagte:
    "// d1.foo(b2); // Geht nicht. d1.foo muss das foo in Derived sein, aber b2 ist kein Derived"

    d1.foo ist doch das foo in derived. Eigentlich können einen Parameter vom Typ der abgeleiten Klasse doch keine Parameter vom Typ der Basisklasse zuweisen werden. Das kann es doch im Endeffekt nur sein!?

    Du hängst zu sehr an der Semantik der Zuweisung. Das ist nicht wichtig für die Frage, wie virtual und Überladungen aufgelöst werden. Wie schon oft hier gesagt, gelten da für Operatoren genau die gleichen Regeln, wie für jede andere Funktion. Mein Beispiel benutzt daher auch absichtlich keine Operatoren, sondern die bedeutungslosen Namen foo und bar.

    Ob es Sinn machen kann, in einer abgeleiteten Klasse eine Funktion zu haben, die Basisklassenreferenzen erwartet? Im allgemeinen ja, da gibt es viele Anwendungsfälle. Für eine Funktion mit der Semantik einer Kopierzuweisung? Nein, das macht wahrscheinlich keinen Sinn, das so zu definieren. Aber wenn man unbedingt wollte, hindert einen auch niemand daran, sinnlosen Code zu schreiben.



  • Danke :)!


Anmelden zum Antworten