Czy dziedziczona klasa może zaimplementować funkcję wirtualną z innym zwracanym typem (bez używania szablonu jako zwrotu)?
Odpowiedzi:
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*
z long
i Derived*
z int
(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):
Zwracany typ funkcji przesłaniającej powinien być identyczny z zwracanym typem funkcji przesłoniętej, albo kowariantny z klasami funkcji. Jeśli funkcja
D::f
przesłania funkcjęB::f
, zwracane typy funkcji są kowariantne, jeśli spełniają następujące kryteria:
- oba są wskaźnikami do klas lub odniesieniami do klas 98)
- klasa w zwracanym typie
B::f
jest tą samą klasą co klasa w zwracanym typieD::f
or, jest jednoznaczną bezpośrednią lub pośrednią klasą bazową klasy w zwracanym typieD::f
i jest dostępna wD
- oba wskaźniki lub odwołania mają tę samą kwalifikację CV, a typ klasy w zwracanym typie
D::f
ma taką samą kwalifikację CV jak kwalifikację CV lub mniej niż typ klasy w zwracanym typieB::f
.
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 funkcji B
, to zwracany typ funkcji w D
musi być podtypem zwracanego typu funkcji w B
. Najczęstszym przykładem jest sytuacja, w której typy zwracane są oparte na D
i B
, 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
oczekuje Base
wskaźnika. Dowolny Base
wskaźnik wystarczy. Tak więc, jeśli D::func
obiecuje, że zawsze zwraca Derived
wskaźnik, to zawsze będzie spełniał kontrakt określony przez klasę nadrzędną, ponieważ każdy Derived
wskaźnik można niejawnie przekonwertować na Base
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 a Derived*
, to D::f
będzie mógł zaakceptować a Base*
. 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.
Implementacja klasy pochodnej funkcji wirtualnej może mieć Covariant Return Type .