2011-01-12 6 views

Antwort

65

In einigen Fällen ist es für eine abgeleitete Klasse zulässig, eine virtuelle Funktion mit einem anderen Rückgabetyp zu überschreiben, solange der Rückgabetyp kovariant mit dem ursprünglichen Rückgabetyp ist. Betrachten wir zum Beispiel die folgenden:

class Base { 
public: 
    virtual ~Base() {} 

    virtual Base* clone() const = 0; 
}; 

class Derived: public Base { 
public: 
    virtual Derived* clone() const { 
     return new Derived(*this); 
    } 
}; 

Hier definiert Base eine rein virtuelle Funktion namens clone, die ein Base * zurückgibt. In der abgeleiteten Implementierung wird diese virtuelle Funktion mit dem Rückgabetyp Derived * überschrieben. Obwohl der Rückgabetyp nicht das gleiche wie in der Basis ist, das ist vollkommen sicher, weil jedes Mal, wenn

Base* ptr = /* ... */ 
Base* clone = ptr->clone(); 

Der Aufruf von clone() wird immer einen Zeiger auf ein Base Objekt schreiben würde, zurückzukehren, da auch wenn es eine gibt Derived*, dieser Zeiger ist implizit in eine Base* umwandelbar und die Operation ist wohldefiniert.

Im Allgemeinen wird der Rückgabetyp einer Funktion nie als Teil der Signatur betrachtet. Sie können eine Elementfunktion mit einem beliebigen Rückgabetyp überschreiben, solange der Rückgabetyp kovariant ist.

+6

Diese "Sie können eine Elementfunktion mit einem beliebigen Rückgabetyp überschreiben" ist nicht korrekt. Sie können überschreiben, solange der Rückgabetyp identisch oder kovariant ist (was Sie erläutert haben). Es gibt hier keinen allgemeineren Fall. – bronekk

+0

@ bronekk- Der Rest des zitierten Satzes besagt dann, dass der neue Rückgabetyp überall dort verwendet werden kann, wo der ursprüngliche Typ wäre; das heißt, der neue Typ ist kovariant mit dem Original. – templatetypedef

+0

, dass der Rest des Satzes nicht korrekt ist; stell dir vor, 'Base *' durch 'long' und' Derived * 'durch' int' zu ersetzen (oder umgekehrt, ist egal). Es wird nicht funktionieren. – bronekk

44

Ja. Die Rückgabetypen können unterschiedlich sein, solange sie covariant sind. Der C++ Standard beschreibt es wie folgt (§10.3/5):

Der Rückgabetyp einer übergeordneten Funktion ist entweder identisch mit dem Rückgabetyp der überschriebene Funktion bzw. kovarianten mit den Klassen der Funktionen sein. Wenn eine Funktion D::f eine Funktion B::f überschreibt, ist der Rückgabetyp der Funktionen covariant, wenn die folgenden Kriterien erfüllen:

  • beide sind Zeiger auf Klassen oder Verweise auf Klassen 98)
  • die Klasse in der Rückgabetyp B::f ist die gleiche Klasse wie die Klasse in dem Rückgabetyp D::f oder ist eine eindeutige direkte oder indirekte Basisklasse der Klasse in dem Rückgabetyp D::f und ist zugänglich in D
  • beiden Zeigern oder Referenzen haben die gleiche cv-Qualifikation und die Klassenart in der Rückgabetyp D::f hat die gleiche cv-Qualifikation als oder weniger cv-Qualifikation als die Klassenart in der Rückgabetyp von B::f.

Fußnote 98 weist darauf hin, dass „Multi-Level Zeiger auf Klassen oder Verweise auf Multi-Level Zeiger auf Klassen sind nicht erlaubt.“

Kurz gesagt, wenn D ein Subtyp von B ist, dann ist der Rückgabetyp der Funktion in D muss in B ein Subtyp des Rückgabetyp der Funktion. Das häufigste Beispiel ist, wenn die Rückgabetypen selbst auf und B basieren, aber das müssen sie nicht sein. Betrachten Sie diese, wo wir zwei separate Typhierarchien:

struct Base { /* ... */ }; 
struct Derived: public Base { /* ... */ }; 

struct B { 
    virtual Base* func() { return new Base; } 
    virtual ~B() { } 
}; 
struct D: public B { 
    Derived* func() { return new Derived; } 
}; 

int main() { 
    B* b = new D; 
    Base* base = b->func(); 
    delete base; 
    delete b; 
} 

Der Grund dies funktioniert, ist, weil jeder Anrufer von func einen Base Zeiger erwartet. Jeder Zeiger Base wird tun. Also, wenn D::func verspricht, immer einen Derived Zeiger zurückzugeben, dann wird es immer den Vertrag von der Ahnenklasse ausgelegt erfüllen, weil jeder Derived Zeiger implizit in einen Base Zeiger umgewandelt werden kann. So bekommen Anrufer immer das, was sie erwarten.


Neben dem Rückgabetyp, einige Sprachen erlauben den Parametertypen der übergeordneten Funktion zu variieren, variieren zu ermöglichen. Wenn sie das tun, müssen sie in der Regel Contravariant sein. Das heißt, wenn B::f eine Derived* akzeptiert, dann könnte D::f eine Base* akzeptieren. Nachkommen dürfen looser in dem, was sie akzeptieren, und strikter in dem, was sie zurückgeben. C++ erlaubt keine Kontravarianz vom Parametertyp. Wenn Sie die Parametertypen ändern, betrachtet C++ sie als eine brandneue Funktion, so dass Sie anfangen, sich zu überladen und zu verstecken.Weitere Informationen zu diesem Thema finden Sie unter Covariance and contravariance (computer science) in Wikipedia.

+0

Nun, ich habe heute etwas gelernt. +1. –

+2

Ist dies ein tatsächliches Merkmal oder ein Nebeneffekt des Rückgabetyps, der bei der Auflösung nicht verwendet wird? –

+1

@Martin, definitiv ein Merkmal. Ich bin mir ziemlich sicher, dass die Überladungsauflösung nichts damit zu tun hat. Der Rückgabetyp * wird * verwendet, wenn Sie eine Funktion überschreiben. –