2012-09-11 66 views
13

que tienen esta pieza de código (ideado a partir de mis problemas de la vida real)Uso de los parámetros de tipo de subclase en funciones virtuales

no puede compilar, quejándose ExtendsB no implementa B::Run(A* a). Sin embargo, no tiene problemas para comprender la extensión de A* Run();

class A { }; 

class ExtendsA : public A { }; 

class B 
{ 
public: 
    virtual ~B(){} 
    virtual void Run(A* a) = 0; 
    virtual A* Run() = 0; 
}; 

class ExtendsB : public B 
{ 
public: 
    virtual ~ExtendsB(){} 

    // Not OK! It does not see it as an implementation of 
    // virtual void Run(A* a) = 0; 
    virtual void Run(ExtendsA* ea) {}; 
    virtual ExtendsA* Run() { return new ExtendsA(); }; // OK 
}; 

Por qué C++ permite cambiar el tipo de retorno a una sub-clase, pero no el tipo de parámetro?

¿Es una buena razón o solo un punto perdido en las especificaciones del idioma?

Respuesta

14

Por qué C++ permite cambiar el tipo de retorno a una subclase , pero no el tipo de parámetro?

C++ estándar permite el uso de un Covariant return type mientras overidding funciones virtuales, pero no permite modificar la función parameters.And sí hay una buena razón detrás de ella.

Justificación:

Anulación esencialmente significa que o bien el método de la clase Base o el método de clase derivada serán llamados en tiempo de ejecución dependiendo del objeto real señalado por el puntero.
implica que:
es decir: "Cada instancia donde se puede invocar el método de clase Base puede reemplazarse por una llamada al método de clase derivada sin ningún cambio en el código de llamada."

Si la regla anterior no estaba en su lugar, dejaría una ventana para romper el código existente mediante la adición de nuevas funcionalidades (nuevas clases derivadas).

Si tiene un prototipo de función en la clase derivada que difiere de los parámetros w.r.t de la función virtual de la clase base, la función no anula la función de la clase base, ya que la regla anterior se rompe.

Sin embargo, los tipos de retorno covariantes no rompen esta regla, porque upcasting sucede de forma implícita y un puntero de clase base siempre puede apuntar a un objeto de clase derivada sin ningún tipo de fundición, por lo tanto, la norma impone esta condición de tipos de retorno covariantes sobre los tipos de retorno.

+2

Para esa regla, los argumentos no necesitan coincidir al 100%. Si bien * los argumentos * covariantes * romperían esa regla, * los argumentos * contra variant * no lo romperían (todos los argumentos que pueden pasarse a la base también se pueden pasar al tipo derivado) y tampoco están permitidos. Parte de la razón es que la x-varianza no viene gratis y debe ser manejada por trampolines/thunks que arreglan los argumentos devueltos (en el caso de la covarianza). Permitir contra varianza en los argumentos aumentaría enormemente (exponencialmente) el número requerido de funciones de trampolín y el tamaño de la tabla virtual. –

8

El error es bastante claro: necesita void Run(A*) en su clase ExtendedB, pero no tiene eso. Todo lo que tiene es void Run(ExtendedA*), pero eso no es lo mismo: la clase base promete aceptar cualquierA -pointer, pero su ExtendedB::Run es más selectivo y solo acepta un subconjunto estrecho.

(Estás confundiendo covarianza y contravarianza, pero eso no es relevante para C++, ya que C++ no permite anulaciones contravariant.)

3

Es simplemente dos tipos diferentes, lo que lo convierte en dos funciones distintas con dos firmas distintas.

En general, si está utilizando un compilador que comprende C++ 11, debe usar la palabra clave override en las funciones que pretenden anular otra función. En su caso, el error se hizo aparente debido a la clase base abstracta, pero en otros casos tal error puede causar una gran depuración ...

+0

+1 por mencionar 'anular'. – xtofl

2

La especialización del tipo de retorno hace que la subclase sea más estricta - actuará como un A. Sin embargo, restringir el método Run para aceptar solo una subclase del argumento original hace que B no actúe como A.

Modelos de herencia y una relación is-a. Tu código viola el principio de sustitución Liskov.

+0

En 2012, C++ ya no se debe considerar un lenguaje de "puro OOP-por puntero" (esto fue cierto solo hasta 1998). Hoy en día, la herencia de C++ es solo un mecanismo de agregación que PUEDE servir a la sustitución de Liskov, pero también puede tener otros propósitos. Las clases C++ no son necesariamente objetos OOP, por lo tanto, la herencia no es necesariamente una relación "is-a": depende del contexto. Una descripción más adecuada es "es implícitamente como un" y no "es un". –

Cuestiones relacionadas