2011-01-12 25 views
60

¿Es posible que una clase heredada implemente una función virtual con un tipo de devolución diferente (que no utiliza una plantilla como resultado)?C++ virtual function return type

Respuesta

65

En algunos casos, sí, es legal que una clase derivada para anular una función virtual usando un retorno diferente tipo, siempre y cuando el tipo de retorno es covariante con el tipo de declaración 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 anula utilizando un tipo de devolución de Derived *. Aunque el tipo de retorno no es el mismo que en la base, esto es perfectamente seguro, porque cada vez que escribiría

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

La llamada a clone() siempre devolverá un puntero a un objeto Base, ya que incluso si se devuelve una Derived*, este puntero es implícitamente convertible a Base* y la operación está bien definida.

Más en general, el tipo de devolución de una función nunca se considera parte de su firma. Puede anular una función de miembro con cualquier tipo de devolución siempre que el tipo de devolución sea covariante.

+6

Este "Puede anular una función de miembro con cualquier tipo de devolución" no es correcto. Puede anular siempre que el tipo de devolución sea idéntico o covariante (lo que usted explicó), punto. No hay un caso más general aquí. – bronekk

+0

@ bronekk- El resto de la oración que citó indica que el nuevo tipo de devolución debe ser utilizable en cualquier parte del tipo original; es decir, el nuevo tipo es covariante con el original. – templatetypedef

+0

que el resto de la oración no es correcto; Imagine reemplazar 'Base *' con 'long' y' Derived * 'con' int' (o al revés, no importa). No funcionará – bronekk

44

Sí. Los tipos de devolución pueden ser diferentes siempre que sean covariant. El estándar de C++ describe así (§ 10.3/5):

El tipo de retorno de una función primordial será, o bien idéntica al tipo de retorno de la función reemplazada o covariante con las clases de las funciones. Si una función D::f anula una función B::f, el tipo de retorno de las funciones son covariantes si el cumplir 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 de D::f o, es una clase base directa o indirecta inequívoca de la clase en el tipo de retorno de D::f y es accesible en D
  • ambos punteros o referencias tienen el la misma calificación de cv y el tipo de clase en el tipo de devolución de D::f tiene la misma calificación de cv o menos cv-qualification que el tipo de clase en el tipo de devolución de B::f.

Nota al pie 98 señala que "punteros de varios niveles a clases o referencias a punteros de varios niveles a clases no están permitidos."

En resumen, si D es un subtipo de B, a continuación, el tipo de retorno de la función en D es necesario que haya 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 tienen que serlo. Considere esto, donde dos jerarquías de tipos distintos:

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 está a la espera de func un puntero Base. Cualquier puntero Base hará. Por lo tanto, si D::func promete devolver siempre un puntero Derived, siempre cumplirá el contrato establecido por la clase antecesora porque cualquier puntero Derived se puede convertir implícitamente en un puntero Base. Por lo tanto, las personas que llaman siempre obtendrán lo que esperan.


Además de permitir el tipo de retorno para variar, algunas lenguas permiten los tipos de parámetros de la función primordial de variar, también. Cuando lo hacen, generalmente deben ser contravariantes. Es decir, si B::f acepta un Derived*, entonces D::f podría aceptar un Base*. Los descendientes pueden tener más flojo en lo que aceptan, y más estricto en lo que devuelven. C++ no permite la contravariancia del tipo de parámetro. Si cambia los tipos de parámetros, C++ considera que es una función completamente nueva, por lo que comienza a sobrecargarse y ocultarse.Para más información sobre este tema, vea Covariance and contravariance (computer science) en Wikipedia.

+0

Bueno, aprendí algo hoy. +1. –

+2

¿Es esta una característica real o un efecto secundario del tipo de devolución que no se utiliza en la resolución? –

+1

@Martin, definitivamente una característica. Estoy bastante seguro de que la resolución de sobrecarga no tiene nada que ver con eso. El tipo de retorno * se * usa si está anulando una función. –