¿Es posible que una clase heredada implemente una función virtual con un tipo de retorno diferente (sin usar una plantilla como retorno)?
¿Es posible que una clase heredada implemente una función virtual con un tipo de retorno diferente (sin usar una plantilla como retorno)?
En algunos casos, sí, es legal que una clase derivada anule una función virtual usando un tipo de retorno diferente siempre que el tipo de retorno sea covariante con el tipo de retorno original. Por ejemplo, considere lo siguiente:
class Base {
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Derived: public Base {
public:
virtual Derived* clone() const {
return new Derived(*this);
}
};
Aquí, Base
define una función virtual pura llamada clone
que devuelve un Base *
. En la implementación derivada, esta función virtual se reemplaza utilizando un tipo de retorno de Derived *
. Aunque el tipo de retorno no es el mismo que en la base, esto es perfectamente seguro porque en cualquier momento escribirías
Base* ptr = /* ... */
Base* clone = ptr->clone();
La llamada a clone()
siempre devolverá un puntero a un Base
objeto, ya que incluso si devuelve a Derived*
, este puntero es implícitamente convertible a a Base*
y la operación está bien definida.
De manera más general, el tipo de retorno de una función nunca se considera parte de su firma. Puede anular una función miembro con cualquier tipo de retorno siempre que el tipo de retorno sea covariante.
Base*
conlong
yDerived*
conint
(o al revés, no importa). No funcionará.Si. Se permite que los tipos de retorno sean diferentes siempre que sean covariantes . El estándar C ++ lo describe así (§10.3 / 5):
La nota al pie 98 señala que "no se permiten punteros de varios niveles a clases o referencias a punteros de varios niveles a clases".
En resumen, si
D
es un subtipo deB
, entonces el tipo de retorno de la función enD
debe ser un subtipo del tipo de retorno de la función enB
. El ejemplo más común es cuando los tipos de devolución se basan enD
yB
, pero no es necesario que lo estén. Considere esto, donde tenemos dos jerarquías de tipos separadas: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; }
La razón por la que esto funciona es porque cualquier persona que llama a
func
está esperando unBase
puntero. CualquierBase
puntero servirá. Por lo tanto, siD::func
promete devolver siempre unDerived
puntero, siempre cumplirá el contrato establecido por la clase antecesora porque cualquierDerived
puntero se puede convertir implícitamente en unBase
puntero. Por lo tanto, las personas que llaman siempre obtendrán lo que esperan.Además de permitir que varíe el tipo de retorno, algunos lenguajes también permiten que varíen los tipos de parámetros de la función primordial. Cuando hacen eso, generalmente necesitan ser contravariantes . Es decir, si
B::f
acepta aDerived*
, seD::f
le permitirá aceptar aBase*
. A los descendientes se les permite ser más flexibles en lo que aceptarán y más estrictos en lo que devolverán. C ++ no permite la contravarianza de tipo de parámetro. Si cambia los tipos de parámetros, C ++ lo considera una función completamente nueva, por lo que comienza a sobrecargarse y ocultarse. Para obtener más información sobre este tema, consulte Covarianza y contravarianza (informática) en Wikipedia.fuente
Una implementación de clase derivada de la función virtual puede tener un tipo de retorno covariante .
fuente