Palabra clave “virtual” de C ++ para funciones en clases derivadas. ¿Es necesario?

Con la definición de estructura dada a continuación …

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

Enfoque n. ° 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 de saludo?

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.

La ‘virtualidad’ de una función se propaga de forma implícita, sin embargo, al menos un comstackdor que uso generará una advertencia si la palabra clave virtual no se usa explícitamente, por lo que puede usarla solo para mantener el comstackdor en silencio.

Desde un punto de vista puramente estilístico, la palabra clave virtual claramente “publicita” el hecho de 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.

La palabra clave virtual no es necesaria en la clase derivada. Aquí está la documentación de respaldo, del Estándar de Borrador C ++ (N3337) (énfasis mío):

10.3 Funciones virtuales

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

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

La omisión de anulación 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 una 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 statement de la función de clase derivada, el comstackdor no podrá decir que tiene la intención de anular una función de una clase base.

Sin embargo, este inconveniente es abordado por la característica del lenguaje de anulación explícita de C ++ 11, que permite que el código fuente especifique claramente que una función miembro está destinada a anular una función de clase 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 comstackdor emitirá un error en tiempo de comstackción y el error de progtwigción será inmediatamente obvio (quizás la función en Derived debería haber tomado un float como argumento).

Consulte WP: C ++ 11 .

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.

No hay diferencia para el comstackdor, cuando escribe lo 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 mostrarle al humano que esta función es virtual.

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

 struct None {}; template struct B : public Interfaces { void hello() { ... } }; struct A { virtual void hello() = 0; }; template void t_hello(const B& 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 b; // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually B* pb = &b; B& 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 ba; // Ok, vtable generated, sizeof(b) >= sizeof(void*) B* pba = &ba; B& 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 es que ahora puede definir funciones de interfaz y no de interfaz 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, incluso podría typedef B a algo si lo desea.

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

Es cuestionable si debe agregar soporte para const A& in 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.

Definitivamente incluiré la palabra clave Virtual para la clase infantil, porque yo. 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.