¿Es posible que una clase heredada implemente una función virtual con un tipo de retorno diferente (sin usar una plantilla como retorno)?
Respuestas:
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*
con long
y Derived*
con int
(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):
El tipo de retorno de una función invalidante será idéntico al tipo de retorno de la función invalidada o covariante con las clases de las funciones. Si una función
D::f
anula una funciónB::f
, el tipo de retorno de las funciones es covariante si satisface los siguientes criterios:
- ambos son punteros a clases o referencias a clases 98)
- la clase en el tipo de retorno de
B::f
es la misma clase que la clase en el tipo de retorno deD::f
o, es una clase base directa o indirecta inequívoca de la clase en el tipo de retorno deD::f
y es accesible enD
- ambos punteros o referencias tienen la misma calificación de cv y el tipo de clase en el tipo de retorno
D::f
tiene la misma calificación de cv o menos que el tipo de clase en el tipo de retorno deB::f
.
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 de B
, entonces el tipo de retorno de la función en D
debe ser un subtipo del tipo de retorno de la función en B
. El ejemplo más común es cuando los tipos de devolución se basan en D
y B
, 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 un Base
puntero. Cualquier Base
puntero servirá. Por lo tanto, si D::func
promete devolver siempre un Derived
puntero, siempre cumplirá el contrato establecido por la clase antecesora porque cualquier Derived
puntero se puede convertir implícitamente en un Base
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 a Derived*
, se D::f
le permitirá aceptar a Base*
. 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.
Una implementación de clase derivada de la función virtual puede tener un tipo de retorno covariante .