virtuelle Methoden: unterschiedlicher Rückgabetyp



  • Hi!

    In einer Basisklasse "Ober" habe ich folgende virtuelle Methode:

    virtual Ober& assign(const Ober& rs)
    {/*...*/}
    

    In einer davon abgeleiteten Klasse "Unter" sieht die entsprechende Methode so aus:

    Unter& assign(const Ober& rs)
    {/*...*/}
    

    Meinem Compiler gefällt das leider nicht:

    error C2555: 'Unter::assign' : Neue Version der virtuellen Funktion unterscheidet sich von 'Ober::assign' nur hinsichtlich des Ergebnistyps oder der Aufrufkonvention
    

    Natürlich ist der Rückgabetyp unterschiedlich, allerdings dachte ich, das sei die einzig zulässige Ausnahme:
    Hat die virtuelle Funktion Rückgabetyp "Referenz auf Klasse", darf der Rückgabetyp der entsprechenden Funktion in der abgeleiteten Klasse "Referenz auf abgeleitete Klasse" sein (genauso für Zeiger).

    Wäre nett, wenn mir jemand erklären könnte, was hier schief läuft.



  • hi,

    Man kann nicht nur den Rückgabetyp ändern.
    Du kannst aber in der abgeleiteten Klassen die selbe Funktion benutzen und innerhalb der Funktion ein return Derived benutzen, da dann Derived automatisch in der Hierarchie upgecastet wird.



  • Um so weniger verstehe ich dann die Fehlermeldung des Compilers...



  • hi,

    ich meins so:

    Basisklasse (Ober):

    virtual Ober& assign(const Ober& rs){/*don't look here, it's top secret*/}
    

    abgeleitete Klasse (Unter):

    virtual Ober& assign(const Ober& rs){return Unter;}
    

    In der abgeleiteten Klasse wird bei dem return die Unterklasse in eine Oberklasse gecastet und zurückgegeben. Du brauchst also keine verschiedenen return-typen, da "Unter" ein "Ober" ist.

    [edit: code tags]



  • Ok, ich denke ich muss doch ein wenig ausholen und das ganze Problem beschreiben: ich habe in einem Lehrbuch ein Kapitel über "Zuweisungsoperator und Vererbung" gelesen. Das folgende Beispiel sollte dabei demonstrieren, wie man das Zuweisungsproblem lösen kann, wenn die Zuweisung auch für polymorphe Objekte funktionieren soll.
    Ich denke, einige der Fragen, die ich dazu habe, würden sich klären, wenn ich das debuggen könnte, allerdings bringt mir mein Compiler die oben erwähnte Fehlermeldung.

    class Ober
    {
    public:
    	Ober(int io)
    		: iO(io){}
    
        virtual Ober& assign(const Ober& rs)
    	{
    		if(this != &rs)
    		{
    			lokaleZuweisung(rs);
    		}
    		return *this;
        }
    
        Ober& operator=(const Ober& rs) { return assign(rs); }
    
    protected:
    	void lokaleZuweisung(const Ober& rs)
    	{
    		iO = rs.iO;
        }
    
    private:
    	int iO;   // Daten speziell für Ober
    };
    
    class Unter : public Ober
    {
    public:
    	Unter(int io, int iu)
    		: Ober(io), iU(iu) {}
    
        virtual Unter& assign(const Ober& rs)
    	{
    		if(this != &rs)
    		{
    			Ober::assign(rs);        // Oberklassensubobjekt
    			/* Die rechte Seite rs wird hier zur Laufzeit in eine
    			Referenz rs mit dem erforderlichen Typ gewandelt.  rs
    			gehört zur Klasse Unter, so dass der Compiler ab hier
    			keine Typkonflikte mehr hat.*/
    			const Unter& U = dynamic_cast<const Unter&>(rs);
    			lokaleZuweisung(U);      // nur lokale Daten
           }
           return *this;
        }
    
        // um Generierung des impliziten Operators zu vermeiden,
        // muss Unter& operator=(const Unter& rs) existieren.
        Unter& operator=(const Unter& rs) { return assign(rs); }
    
    protected:
        void lokaleZuweisung(const Unter& rs)
    	{
    		iU = rs.iU;
        }
    
    private:
        int iU;   // Daten speziell für Unter
    };
    

    Nehmen wir an, ich hätte folgende Variablendeklarationen:

    Unter U1, U2;
    Ober  O1, O2;
    Ober& rO = U2; // polymorph
    

    Und folgende Zuweisungen (mit den Fragen, die sich dabei für mich ergeben):

    O1 = O2;  // kein Problem, verstehe ich :-)
    U1 = U2;  // dito
    O1 = U1;  // funktioniert, ich muss mir halt klar sein, dass ich Information verliere
    U1 = O1;  // ohnehin verboten => Fehler
    // jetzt kommen die interessanten Fälle
    O1 = rO;  // zunächst wird Ober::operator=() aufgerufen, 
              // der ruft assign(rO) auf und weil assign virtuell ist, wird
              // Unter::assign() aufgerufen. Das liefert Unter& zurück und
              // ich habe wieder den Fall O1 = U1. Ist das korrekt?
              // Weitere Frage: was ist in diesem Fall this? Das temporäre Objekt,
              // das in return assign(rs) steckt?
    U1 = rO;  // es wird Unter::operator=() augerufen und wenn die vorherige
              // Zuweisung korrekt beschrieben war, ist mir diese auch klar ;-)
    

    Wäre nett, wenn mich jemand bestätigen oder den Müll, den ich da erzähle korrigieren könnte!!

    Und meine anfängliche Frage bleibt: warum beschwert sich der Compiler und was tue ich dagegen ohne Folgefehler zu erhalten?

    Vielen Dank schon mal vorab!!!



  • Das ist ein Bug in älteren Versionen von Visual C++:
    http://support.microsoft.com/?scid=kb%3Ben-us%3B240862

    Mit Visual C++.NET 2003 geht das.



  • Das ist ja interessant! Und ärgerlich für mich 😞
    Trotzdem danke für die Info!!

    Da ein Debug also auch zukünftig nicht möglich sein wird, wäre ich jetzt noch dankbar, wenn sich jemand zu meinen Kommentaren im letzten Quellcodeblock äußern könnte...

    MfG,
    Nanuq



  • Nanuq schrieb:

    O1 = O2;  // kein Problem, verstehe ich :-)
    U1 = U2;  // dito
    O1 = U1;  // funktioniert, ich muss mir halt klar sein, dass ich Information verliere
    U1 = O1;  // ohnehin verboten => Fehler
    

    Bis hierher dürfte es richtig sein.

    O1 = rO;  // zunächst wird Ober::operator=() aufgerufen, 
              // der ruft assign(rO) auf und weil assign virtuell ist, wird
              // Unter::assign() aufgerufen. Das liefert Unter& zurück und
              // ich habe wieder den Fall O1 = U1. Ist das korrekt?
              // Weitere Frage: was ist in diesem Fall this? Das temporäre Objekt,
              // das in return assign(rs) steckt?
    

    Nein, das ruft Ober::assign(r0) auf (wichtig ist hierfür der Typ links vom Gleichheitszeichen)

    U1 = rO;  // es wird Unter::operator=() augerufen und wenn die vorherige
              // Zuweisung korrekt beschrieben war, ist mir diese auch klar ;-)
    

    Das dürfte genauso in die Hose gehen wie "U1=O1;" (ob es möglich ist, entscheiden die statischen Typen der Objekte).

    btw, interessanter dürfte die Zuweisung "rO=O1;" sein - hier greift das virtual und du rufst Unter::assign mit einem echten Ober-Objekt auf -> Crash (bzw. Exception im dynamic_cast).



  • Danke erstmal für's durchlesen!

    CStoll schrieb:

    O1 = rO;  // zunächst wird Ober::operator=() aufgerufen, 
              // der ruft assign(rO) auf und weil assign virtuell ist, wird
              // Unter::assign() aufgerufen. Das liefert Unter& zurück und
              // ich habe wieder den Fall O1 = U1. Ist das korrekt?
              // Weitere Frage: was ist in diesem Fall this? Das temporäre Objekt,
              // das in return assign(rs) steckt?
    

    Nein, das ruft Ober::assign(r0) auf (wichtig ist hierfür der Typ links vom Gleichheitszeichen)

    Ok, verstehe ich: links des Gleichheitszeichens steht ein "Ober" deshalb zuerst Aufruf Ober::operator=(rO), innerhalb der Methode dann Ober::assign(rO), d.h. die zusätzlichen Attribute von "Unter" gehen wie im Fall O1=U1 verloren.

    CStoll schrieb:

    U1 = rO;  // es wird Unter::operator=() augerufen und wenn die vorherige
              // Zuweisung korrekt beschrieben war, ist mir diese auch klar ;-)
    

    Das dürfte genauso in die Hose gehen wie "U1=O1;" (ob es möglich ist, entscheiden die statischen Typen der Objekte).

    Das heißt, hier scheitert bereits der Aufruf von Unter::operator=(rO), weil ein "Unter" als Übergabe erwartet, aber (statisch gesehen) ein "Ober" übergeben wird, sodass assign(rO) gar nicht erst aufgerufen wird??

    CStoll schrieb:

    btw, interessanter dürfte die Zuweisung "rO=O1;" sein - hier greift das virtual und du rufst Unter::assign mit einem echten Ober-Objekt auf -> Crash (bzw. Exception im dynamic_cast).

    Das klingt logisch! D.h. die vom Autor des Buches angegebene Lösung einer Zuweisung, die auch für polymorphe Objekte funktioniert, ist ziemlicher Blödsinn, weil sie für die beiden Fälle

    U1=rO;
    rO=O1;
    

    nicht funktioniert und die virtuelle Funktion assign eigetnlich überhaupt keinen Nutzen hat...😕
    Vielleicht sollte ich mich nach einem anderen Buch umschauen...


Anmelden zum Antworten