Anular de forma segura las funciones virtuales de C ++

100

Tengo una clase base con una función virtual y quiero anular esa función en una clase derivada. ¿Hay alguna forma de hacer que el compilador verifique si la función que declaré en la clase derivada en realidad anula una función en la clase base? Me gustaría agregar alguna macro o algo que asegure que no declare accidentalmente una nueva función, en lugar de anular la anterior.

Toma este ejemplo:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Aquí parent::handle_event()se llama en lugar de child::handle_event(), porque el método del niño pierde la constdeclaración y, por lo tanto, declara un nuevo método. Esto también podría ser un error tipográfico en el nombre de la función o una pequeña diferencia en los tipos de parámetros. También puede suceder fácilmente si la interfaz de la clase base cambia y en algún lugar no se actualizó alguna clase derivada para reflejar el cambio.

¿Hay alguna forma de evitar este problema? ¿Puedo decirle al compilador o alguna otra herramienta que verifique esto por mí? ¿Algún indicador de compilador útil (preferiblemente para g ++)? ¿Cómo evitas estos problemas?

algo
fuente
2
Gran pregunta, he estado tratando de averiguar por qué no se llama a mi función de clase secundaria desde hace una hora.
Akash Mankar

Respuestas:

89

Desde g ++ 4.7, comprende la nueva overridepalabra clave de C ++ 11 :

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};
Gunther Piez
fuente
@hirschhornsalz: Descubrí que cuando implementas la función handle_event y agregas override al final de la implementación de la función, g ++ da un error; si proporciona una implementación de función en línea en la declaración de clase que sigue a la palabra clave override, todo está bien. ¿Por qué?
h9uest
3
@ h9uest overridedebe usarse en la definición. Una implementación en línea es tanto definición como implementación, así que está bien.
Gunther Piez
@hirschhornsalz sí, obtuve el mismo mensaje de error por g ++. Sin embargo, una nota al margen: tanto tú como g ++ error msg usaron el término "definición de clase" - ¿no deberíamos usar "declaración" ({declaración, definición} par)? Lo dejó claro en este contexto particular al decir "definición e implementación", pero me pregunto por qué la comunidad de C ++ de repente decide cambiar los términos en las clases.
h9uest
20

Algo como la overridepalabra clave de C # no es parte de C ++.

En gcc, -Woverloaded-virtualadvierte contra la ocultación de una función virtual de clase base con una función del mismo nombre pero con una firma lo suficientemente diferente como para no anularla. Sin embargo, no lo protegerá contra la falla al anular una función debido a una mala ortografía del nombre de la función.

CB Bailey
fuente
2
Es si está utilizando Visual C ++
Steve Rowe
4
El uso de Visual C ++ no crea overrideuna palabra clave en C ++; Sin embargo, podría significar que está utilizando algo que puede compilar código fuente C ++ no válido. ;)
CB Bailey
3
El hecho de que la anulación sea C ++ inválida significa que el estándar es incorrecto, y no Visual C ++
Jon
2
@Jon: Bien, ahora veo a dónde te dirigías. Personalmente, podría tomar o dejar la overridefuncionalidad de estilo C # ; Rara vez he tenido problemas con anulaciones fallidas y han sido relativamente fáciles de diagnosticar y solucionar. Uno con lo que no estaré de acuerdo es que los usuarios de VC ++ deberían usarlo. Preferiría que C ++ se parezca a C ++ en todas las plataformas, incluso si un proyecto en particular no necesita ser portátil. Vale la pena señalar que C ++ 0x tendrá atributos y [[base_check]], por lo tanto, puede optar por anular la verificación si lo desea. [[override]][[hiding]]
CB Bailey
5
Curiosamente, un par de años después de que el comentario usando VC ++ no sea overrideuna palabra clave , parece que . Bueno, no es una palabra clave adecuada, sino un identificador especial en C ++ 11. Microsoft presionó lo suficiente para hacer un caso especial de esto y seguir el formato general de atributos y overrideconvertirlo en el estándar :)
David Rodríguez - dribeas
18

Hasta donde yo sé, ¿no puedes simplemente hacerlo abstracto?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Pensé haber leído en www.parashift.com que en realidad se puede implementar un método abstracto. Lo que tiene sentido para mí personalmente, lo único que hace es obligar a las subclases a implementarlo, nadie dijo nada sobre que no se le permita tener una implementación en sí.

Ray Hidayat
fuente
¡Solo ahora me he dado cuenta de que esto funciona en más que solo destructores! Gran hallazgo.
extraño
1
Hay un par de posibles inconvenientes en esto: 1) la otra cosa que marcar uno o más métodos como abstractos es hacer que la clase base no sea instanciable, lo que puede ser un problema si eso no es parte del uso previsto de la clase. 2) la clase base podría no ser suya para modificarla en primer lugar.
Michael Burr
3
Estoy de acuerdo con Michael Burr. Hacer el resumen de la clase base no es parte de la cuestión. Es perfectamente razonable tener una clase base con funcionalidad en un método virtual, que desea que anule una clase derivada. Y es igualmente razonable querer protegerse contra otro programador que cambie el nombre de la función en la clase base y haga que su clase derivada ya no la anule. La extensión de "invalidación" de Microsoft es invaluable en este caso. Me encantaría verlo agregado al estándar, porque desafortunadamente no hay una buena manera de hacer esto sin él.
Brian
Esto también evita que el método base (digamos BaseClass::method()) sea llamado en la implementación derivada (digamos DerivedClass::method()), por ejemplo, para un valor predeterminado.
Narcolessico
11

En MSVC, puede utilizar la overridepalabra clave CLR incluso si no está compilando para CLR.

En g ++, no hay una forma directa de hacer cumplir eso en todos los casos; otras personas han dado buenas respuestas sobre cómo detectar diferencias de firma utilizando -Woverloaded-virtual. En una versión futura, alguien podría agregar una sintaxis similar __attribute__ ((override))o equivalente usando la sintaxis C ++ 0x.

Doug
fuente
9

En MSVC ++ puedes usar palabras claveoverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override funciona tanto para código nativo como CLR en MSVC ++.

bobobobo
fuente
5

Haga que la función sea abstracta, de modo que las clases derivadas no tengan otra opción que anularla.

@Ray Tu código no es válido.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Las funciones abstractas no pueden tener cuerpos definidos en línea. Debe modificarse para convertirse

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }
Tanveer Badar
fuente
3

Sugeriría un ligero cambio en su lógica. Puede funcionar o no, dependiendo de lo que necesite lograr.

handle_event () todavía puede hacer el "código predeterminado aburrido", pero en lugar de ser virtual, en el punto en el que desea que haga el "nuevo código emocionante", haga que la clase base llame a un método abstracto (es decir, debe ser anulado) que será proporcionado por su clase descendiente.

EDITAR: Y si luego decide que algunas de sus clases descendientes no necesitan proporcionar un "nuevo código interesante", entonces puede cambiar el resumen a virtual y proporcionar una implementación de clase base vacía de esa funcionalidad "insertada".

JMD
fuente
2

Su compilador puede tener una advertencia que puede generar si una función de clase base se oculta. Si es así, habilítelo. Eso detectará conflictos constantes y diferencias en las listas de parámetros. Desafortunadamente, esto no revelará un error de ortografía.

Por ejemplo, esta es la advertencia C4263 en Microsoft Visual C ++.

Mark Ransom
fuente
1

La overridepalabra clave C ++ 11 cuando se usa con la declaración de función dentro de la clase derivada, obliga al compilador a verificar que la función declarada esté anulando alguna función de la clase base. De lo contrario, el compilador arrojará un error.

Por lo tanto, puede usar el overrideespecificador para garantizar el polimorfismo dinámico (anulación de función).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
Adarsh ​​Kumar
fuente