2011-02-04 9 views
167

Con la definición estructura dada a continuación ...Palabra clave "virtual" de C++ para funciones en clases derivadas. ¿Es necesario?

struct A { 
    virtual void hello() = 0; 
}; 

Enfoque # 1:

struct B : public A { 
    virtual void hello() { ... } 
}; 

Enfoque # 2:

struct B : public A { 
    void hello() { ... } 
}; 

¿Hay alguna diferencia entre estas dos formas de anular la función hola?

+55

En C++ 11 puede escribir "void hello() override {}" para declarar explícitamente que está anulando un método virtual. El compilador fallará si no existe un método virtual base, y tiene la misma legibilidad que colocar "virtual" en la clase descendiente. – ShadowChaser

+0

En realidad, en C++ 11 de gcc, escribir invalidar hello() anular {} en la clase derivada es correcto porque la clase base ha especificado que el método hello() es virtual. En otras palabras, el uso de la palabra virtual en la clase _derived_ no es necesario/obligatorio, para gcc/g ++ de todos modos. (Estoy usando la versión 4.9.2 de gcc en un RPi 3). Pero es una buena práctica incluir de todos modos la palabra clave virtual en el método de la clase derivada. – Will

Respuesta

138

Son exactamente lo mismo. No hay diferencia entre ellos aparte de que el primer enfoque requiere más tipeo y es potencialmente más claro.

+18

Esto es cierto, pero [la Guía de Mozilla C++ Portability] (https://developer.mozilla.org/en/C___Portability_Guide#Use_virtual_declaration_on_allubstant_virtual_member_functions) recomienda utilizar siempre virtual porque "algunos compiladores" emiten advertencias si no lo hace. Lástima que no mencionen ningún ejemplo de tales compiladores. –

+4

También agregaría que marcarlo explícitamente como virtual lo ayudará a recordarle que también debe hacer que el destructor sea virtual. – lfalin

+1

Solo para mencionar, lo mismo aplicable a [virtual destructor] (http: // stackoverflow.com/questions/2198379/are-virtual-destructores-inherited) – Atul

7

No hay diferencia para el compilador, cuando escribe virtual en la clase derivada u omitirlo.

Pero necesita ver la clase base para obtener esta información. Por lo tanto, recomendaría agregar la palabra clave virtual también en la clase derivada, si desea mostrar al ser humano que esta función es virtual.

67

La 'virtualidad' de una función se propaga implícitamente; sin embargo, al menos un compilador que utilizo generará una advertencia si la palabra clave virtual no se usa explícitamente, por lo que puede utilizarla solo para mantener el compilador en silencio.

Desde un punto de vista puramente estilístico, la palabra clave virtual claramente "anuncia" al usuario que la función es virtual. Esto será importante para cualquier otra subclase B sin tener que verificar la definición de A. Para las jerarquías de clase profunda, esto se vuelve especialmente importante.

+9

¿Qué compilador es este? –

+27

@James: armcc (compilador de ARM para dispositivos ARM) – Clifford

+1

Interesante. Gracias por la información. –

9

Agregar la palabra clave "virtual" es una buena práctica, ya que mejora la legibilidad, pero no es necesario. Las funciones declaradas virtuales en la clase base y que tienen la misma firma en las clases derivadas se consideran "virtuales" de forma predeterminada.

0

Definitivamente incluiré la palabra clave Virtual para la clase secundaria, porque i. Legibilidad. ii. Esta clase hija se derivará más abajo, no quiere que el constructor de la clase derivada posterior llame a esta función virtual.

+0

No entiendo el punto 'ii'. ¿Podrías ampliarlo? –

+0

Creo que quiere decir que sin marcar la función hija como virtual, un programador que deriva de la clase secundaria más adelante puede no darse cuenta de que la función es virtual (porque nunca miró la clase base) y potencialmente puede llamarla durante la construcción (que puede o no hacer lo correcto). – PfhorSlayer

29

La palabra clave virtual no es necesaria en la clase derivada. Aquí está la documentación de respaldo, desde el C++ Proyecto de Norma (N3337) (el subrayado es mío):

10,3 funciones virtuales

2 Si una función miembro virtual vf se declara en una clase Base y en una clase Derived, derivada directa o indirectamente de Base, una función de miembro vf con el mismo nombre, parameter-type-list (8.3.5), cv-qualification, y ref-qualifier (o la ausencia de la misma) como Base::vf se declara, entonces Derived::vf también es virtual (si o n ot está declarado) e invalida Base::vf.

23

No, la palabra clave virtual en las funciones derivadas de las anulaciones de funciones virtuales no es necesaria.Pero vale la pena mencionar una trampa relacionada: una falla al anular una función virtual.

La omisión de omite se produce si tiene la intención de anular una función virtual en una clase derivada, pero comete un error en la firma para que declare una función virtual nueva y diferente. Esta función puede ser sobrecarga de la función de clase base, o puede diferir en el nombre. Ya sea que utilice o no la palabra clave virtual en la declaración de función de clase derivada, el compilador no podrá decir que tiene la intención de anular una función de una clase base.

este escollo es, sin embargo, afortunadamente dirigida por la característica del lenguaje C++ 11 explicit override, que permite que el código fuente para especificar claramente que una función miembro está destinado para anular una función de clase de base:

struct Base { 
    virtual void some_func(float); 
}; 

struct Derived : Base { 
    virtual void some_func(int) override; // ill-formed - doesn't override a base class method 
}; 

El compilador emitirá un error en tiempo de compilación y el error de programación será inmediatamente obvio (quizás la función en Derived debería haber tomado un float como argumento).

Consulte WP:C++11.

1

Hay una diferencia considerable cuando se tiene plantillas y empezar a tomar clase base (s) como parámetro de plantilla (s):

struct None {}; 

template<typename... Interfaces> 
struct B : public Interfaces 
{ 
    void hello() { ... } 
}; 

struct A { 
    virtual void hello() = 0; 
}; 

template<typename... Interfaces> 
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly 
{ 
    b.hello(); // indirect, non-virtual call 
} 

void hello(const A& a) 
{ 
    a.hello(); // Indirect virtual call, inlining is impossible in general 
} 

int main() 
{ 
    B<None> b;   // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually 
    B<None>* pb = &b; 
    B<None>& rb = b; 

    b.hello();   // direct call 
    pb->hello();  // pb-relative non-virtual call (1 redirection) 
    rb->hello();  // non-virtual call (1 redirection unless optimized out) 
    t_hello(b);   // works as expected, one redirection 
    // hello(b);  // compile-time error 


    B<A>  ba;  // Ok, vtable generated, sizeof(b) >= sizeof(void*) 
    B<None>* pba = &ba; 
    B<None>& rba = ba; 

    ba.hello();   // still can be a direct call, exact type of ba is deducible 
    pba->hello();  // pba-relative virtual call (usually 3 redirections) 
    rba->hello();  // rba-relative virtual call (usually 3 redirections unless optimized out to 2) 
    //t_hello(b);  // compile-time error (unless you add support for const A& in t_hello as well) 
    hello(ba); 
} 

La parte divertida de esto es que ahora se puede definir la interfaz y no la interfaz funciones más tarde para definir clases. Esto es útil para interfaces de interfuncionamiento entre bibliotecas (no confíe en esto como un proceso de diseño estándar de una sola biblioteca ). No le cuesta nada permitir esto para todas sus clases; puede incluso typedef B si lo desea.

Tenga en cuenta que, si hace esto, también puede declarar los constructores de copiar/mover como plantillas: permitir construir desde diferentes interfaces le permite 'fundir' entre diferentes tipos de B<>.

Es cuestionable si debe agregar soporte para const A& en t_hello(). La razón habitual para esta reescritura es alejarse de la especialización basada en herencia a la basada en plantillas, principalmente por motivos de rendimiento. Si continúa admitiendo la interfaz anterior, difícilmente podrá detectar (o disuadir) el uso anterior.

Cuestiones relacionadas