Czy dziedziczona klasa może zaimplementować funkcję wirtualną z innym zwracanym typem (bez używania szablonu jako zwrotu)?
Czy dziedziczona klasa może zaimplementować funkcję wirtualną z innym zwracanym typem (bez używania szablonu jako zwrotu)?
W niektórych przypadkach tak, klasa pochodna może przesłonić funkcję wirtualną przy użyciu innego typu zwracanego, o ile typ zwracany jest kowariantny z pierwotnym typem zwracanym. Na przykład rozważ następujące kwestie:
class Base {
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Derived: public Base {
public:
virtual Derived* clone() const {
return new Derived(*this);
}
};
Tutaj Base
definiuje czystą funkcję wirtualną o nazwie, clone
która zwraca plik Base *
. W implementacji pochodnej ta funkcja wirtualna jest zastępowana przy użyciu zwracanego typu Derived *
. Chociaż typ zwrotu nie jest taki sam jak w bazie, jest to całkowicie bezpieczne, ponieważ w każdej chwili możesz napisać
Base* ptr = /* ... */
Base* clone = ptr->clone();
Wywołanie clone()
to zawsze zwróci wskaźnik do Base
obiektu, ponieważ nawet jeśli zwraca a Derived*
, ten wskaźnik jest niejawnie konwertowany na a, Base*
a operacja jest dobrze zdefiniowana.
Mówiąc bardziej ogólnie, typ zwracany funkcji nigdy nie jest uważany za część jej podpisu. Funkcję składową można zastąpić dowolnym typem zwracanym, o ile typ zwracany jest kowariantny.
Base*
zlong
iDerived*
zint
(lub na odwrót, nie ma znaczenia). To nie zadziała.Tak. Typy zwracane mogą być różne, o ile są kowariantne . Standard C ++ opisuje to w następujący sposób (§ 10.3 / 5):
Przypis 98 wskazuje, że „wielopoziomowe wskaźniki do klas lub odniesienia do wielopoziomowych wskaźników do klas są niedozwolone”.
W skrócie, jeśli
D
jest podtypem funkcjiB
, to zwracany typ funkcji wD
musi być podtypem zwracanego typu funkcji wB
. Najczęstszym przykładem jest sytuacja, w której typy zwracane są oparte naD
iB
, ale nie muszą. Rozważ to, gdy mamy dwie oddzielne hierarchie typów: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; }
Powodem, dla którego to działa, jest to, że każdy wywołujący
func
oczekujeBase
wskaźnika. DowolnyBase
wskaźnik wystarczy. Tak więc, jeśliD::func
obiecuje, że zawsze zwracaDerived
wskaźnik, to zawsze będzie spełniał kontrakt określony przez klasę nadrzędną, ponieważ każdyDerived
wskaźnik można niejawnie przekonwertować naBase
wskaźnik. W ten sposób dzwoniący zawsze otrzymają to, czego oczekują.Oprócz zezwalania na zmianę typu zwracanego, niektóre języki pozwalają również na zmianę typów parametrów funkcji przesłaniającej. Kiedy to robią, zwykle muszą być sprzeczne . Oznacza to, że jeśli
B::f
akceptuje aDerived*
, toD::f
będzie mógł zaakceptować aBase*
. Potomkowie mogą być bardziej rozluźnieni w tym, co przyjmą, i surowsi w tym, co zwrócą. C ++ nie zezwala na kontrawariancję typu parametru. Jeśli zmienisz typy parametrów, C ++ uzna to za zupełnie nową funkcję, więc zaczniesz się przeciążać i ukrywać. Więcej informacji na ten temat można znaleźć w artykule Kowariancja i kontrawariancja (informatyka) w Wikipedii.źródło
Implementacja klasy pochodnej funkcji wirtualnej może mieć Covariant Return Type .
źródło