virtual
function specifier
Especifica que una función miembro no estática es virtual y soporta despacho dinámico. Solo puede aparecer en la decl-specifier-seq de la declaración inicial de una función miembro no estática (es decir, cuando se declara en la definición de la clase).
Contenidos |
Explicación
Las funciones virtuales son funciones miembro cuyo comportamiento puede ser sobrescrito en clases derivadas. A diferencia de las funciones no virtuales, el comportamiento de sobrescritura se preserva incluso si no hay información en tiempo de compilación sobre el tipo real de la clase. Es decir, si una clase derivada se maneja usando un puntero o referencia a la clase base, una llamada a una función virtual sobrescrita invocaría el comportamiento definido en la clase derivada. Tal llamada de función se conoce como
virtual function call
o
virtual call
. La virtual function call se suprime si la función se selecciona usando
qualified name lookup
(es decir, si el nombre de la función aparece a la derecha del operador de resolución de ámbito
::
).
#include <iostream> struct Base { virtual void f() { std::cout << "base\n"; } }; struct Derived : Base { void f() override // 'override' es opcional { std::cout << "derived\n"; } }; int main() { Base b; Derived d; // llamada a función virtual mediante referencia Base& br = b; // el tipo de br es Base& Base& dr = d; // el tipo de dr también es Base& br.f(); // imprime "base" dr.f(); // imprime "derived" // llamada a función virtual mediante puntero Base* bp = &b; // el tipo de bp es Base* Base* dp = &d; // el tipo de dp también es Base* bp->f(); // imprime "base" dp->f(); // imprime "derived" // llamada a función no virtual br.Base::f(); // imprime "base" dr.Base::f(); // imprime "base" }
En detalle
Si alguna función miembro
vf
está declarada como
virtual
en una clase
Base
, y alguna clase
Derived
, que deriva, directa o indirectamente, de
Base
, tiene una declaración para función miembro con el mismo
- nombre
- lista de tipos de parámetros (pero no el tipo de retorno)
- calificadores cv
- calificadores de referencia
Entonces esta función en la clase
Derived
también es
virtual
(independientemente de si se usa la palabra clave
virtual
en su declaración) y
sobreescribe
Base::vf (independientemente de si se usa el especificador
override
en su declaración).
Base::vf
no necesita ser accesible o visible para ser sobrescrito. (
Base::vf
puede declararse como privado, o
Base
puede heredarse usando herencia privada. Cualquier miembro con el mismo nombre en una clase base de
Derived
que herede
Base
no importa para la determinación de sobrescritura, incluso si ocultarían
Base::vf
durante la búsqueda de nombres.)
class B { virtual void do_f(); // miembro privado public: void f() { do_f(); } // interfaz pública }; struct D : public B { void do_f() override; // sobrescribe B::do_f }; int main() { D d; B* bp = &d; bp->f(); // internamente llama a D::do_f(); }
Para cada función virtual, existe el
final overrider
, que se ejecuta cuando se realiza una llamada a función virtual. Una función miembro virtual
vf
de una clase base
Base
es el final overrider a menos que la clase derivada declare o herede (a través de herencia múltiple) otra función que sobrescriba
vf
.
struct A { virtual void f(); }; // A::f es virtual struct B : A { void f(); }; // B::f sobreescribe A::f en B struct C : virtual B { void f(); }; // C::f sobreescribe A::f en C struct D : virtual B {}; // D no introduce un sobreescribidor, B::f es final en D struct E : C, D // E no introduce un sobreescribidor, C::f es final en E { using A::f; // no es una declaración de función, solo hace visible A::f para búsqueda }; int main() { E e; e.f(); // llamada virtual invoca C::f, el sobreescribidor final en e e.E::f(); // llamada no virtual invoca A::f, que es visible en E }
Si una función tiene más de un final overrider, el programa está mal formado:
struct A { virtual void f(); }; struct VB1 : virtual A { void f(); // anula A::f }; struct VB2 : virtual A { void f(); // anula A::f }; // struct Error : VB1, VB2 // { // // Error: A::f tiene dos anuladores finales en Error // }; struct Okay : VB1, VB2 { void f(); // OK: este es el anulador final para A::f }; struct VB1a : virtual A {}; // no declara un anulador struct Da : VB1a, VB2 { // en Da, el anulador final de A::f es VB2::f };
Una función con el mismo nombre pero lista de parámetros diferente no anula la función base del mismo nombre, sino que oculta la función base: cuando la búsqueda de nombre no calificada examina el ámbito de la clase derivada, la búsqueda encuentra la declaración y no examina la clase base.
struct B { virtual void f(); }; struct D : B { void f(int); // D::f oculta B::f (lista de parámetros incorrecta) }; struct D2 : D { void f(); // D2::f sobrescribe B::f (no importa que no sea visible) }; int main() { B b; B& b_as_b = b; D d; B& d_as_b = d; D& d_as_d = d; D2 d2; B& d2_as_b = d2; D& d2_as_d = d2; b_as_b.f(); // llama a B::f() d_as_b.f(); // llama a B::f() d2_as_b.f(); // llama a D2::f() d_as_d.f(); // Error: la búsqueda en D encuentra solo f(int) d2_as_d.f(); // Error: la búsqueda en D encuentra solo f(int) }
|
Si una función se declara con el especificador
struct B { virtual void f(int); }; struct D : B { virtual void f(int) override; // OK, D::f(int) sobreescribe B::f(int) virtual void f(long) override; // Error: f(long) no sobreescribe B::f(int) };
Si una función se declara con el especificador
struct B { virtual void f() const final; }; struct D : B { void f() const; // Error: D::f intenta sobreescribir la función final B::f }; |
(desde C++11) |
Las funciones no miembro y las funciones miembro estáticas no pueden ser virtuales.
Las plantillas de función no pueden declararse
virtual
. Esto se aplica únicamente a funciones que son plantillas en sí mismas - una función miembro regular de una plantilla de clase puede declararse virtual.
|
Las funciones virtuales (ya sea declaradas virtuales o que anulan una) no pueden tener restricciones asociadas. struct A { virtual void f() requires true; // Error: constrained virtual function };
Una función virtual
|
(desde C++20) |
Argumentos predeterminados para funciones virtuales se sustituyen en tiempo de compilación.
Tipos de retorno covariantes
Si la función
Derived::f
sobrescribe una función
Base::f
, sus tipos de retorno deben ser iguales o ser
covariantes
. Dos tipos son covariantes si cumplen todos los siguientes requisitos:
- ambos tipos son punteros o referencias (lvalue o rvalue) a clases. No se permiten punteros o referencias multinivel.
-
la clase referenciada/apuntada en el tipo de retorno de
Base::f()debe ser una clase base directa o indirecta, inequívoca y accesible de la clase referenciada/apuntada del tipo de retorno deDerived::f(). -
el tipo de retorno de
Derived::f()debe ser igual o menos cv-qualified que el tipo de retorno deBase::f().
La clase en el tipo de retorno de
Derived::f
debe ser ya sea
Derived
misma, o debe ser un
tipo completo
en el punto de declaración de
Derived::f
.
Cuando se realiza una llamada a una función virtual, el tipo devuelto por el final overrider es convertido implícitamente al tipo de retorno de la función sobrescrita que fue llamada:
class B {}; struct Base { virtual void vf1(); virtual void vf2(); virtual void vf3(); virtual B* vf4(); virtual B* vf5(); }; class D : private B { friend struct Derived; // en Derived, B es una base accesible de D }; class A; // clase declarada previamente es un tipo incompleto struct Derived : public Base { void vf1(); // virtual, sobreescribe Base::vf1() void vf2(int); // no virtual, oculta Base::vf2() // char vf3(); // Error: sobreescribe Base::vf3, pero tiene diferente // y tipo de retorno no covariante D* vf4(); // sobreescribe Base::vf4() y tiene tipo de retorno covariante // A* vf5(); // Error: A es un tipo incompleto }; int main() { Derived d; Base& br = d; Derived& dr = d; br.vf1(); // llama a Derived::vf1() br.vf2(); // llama a Base::vf2() // dr.vf2(); // Error: vf2(int) oculta vf2() B* p = br.vf4(); // llama a Derived::vf4() y convierte el resultado a B* D* q = dr.vf4(); // llama a Derived::vf4() y no convierte el resultado a B* }
Destructor virtual
Aunque los destructores no se heredan, si una clase base declara su destructor
virtual
, el destructor derivado siempre lo anula. Esto hace posible eliminar objetos asignados dinámicamente de tipo polimórfico a través de punteros a la base.
class Base { public: virtual ~Base() { /* libera los recursos de Base */ } }; class Derived : public Base { ~Derived() { /* libera los recursos de Derived */ } }; int main() { Base* b = new Derived; delete b; // Realiza una llamada virtual a Base::~Base() // al ser virtual, llama a Derived::~Derived() que puede // liberar los recursos de la clase derivada, y luego llama // a Base::~Base() siguiendo el orden habitual de destrucción }
Además, si el destructor de la clase base no es virtual, eliminar un objeto de la clase derivada a través de un puntero a la clase base es comportamiento indefinido independientemente de si hay recursos que podrían filtrarse si no se invoca el destructor derivado , a menos que la función de desasignación seleccionada sea un operator delete destructor (desde C++20) .
Una guía útil es que el destructor de cualquier clase base debe ser público y virtual o protegido y no virtual , siempre que estén involucradas expresiones delete , por ejemplo, cuando se usa implícitamente en std::unique_ptr (desde C++11) .
Durante la construcción y destrucción
Cuando una función virtual se llama directa o indirectamente desde un constructor o desde un destructor (incluyendo durante la construcción o destrucción de los miembros de datos no estáticos de la clase, p.ej. en una lista de inicializadores ), y el objeto al que se aplica la llamada es el objeto en construcción o destrucción, la función llamada es el final overrider en la clase del constructor o destructor y no una que la reemplace en una clase más derivada. En otras palabras, durante la construcción o destrucción, las clases más derivadas no existen.
Al construir una clase compleja con múltiples ramas, dentro de un constructor que pertenece a una rama, el polimorfismo se restringe a esa clase y sus bases: si obtiene un puntero o referencia a un subobjeto base fuera de esta subjerarquía, e intenta invocar una llamada a función virtual (por ejemplo, usando acceso explícito a miembro), el comportamiento es indefinido:
struct V { virtual void f(); virtual void g(); }; struct A : virtual V { virtual void f(); // A::f es el reemplazador final de V::f en A }; struct B : virtual V { virtual void g(); // B::g es el reemplazador final de V::g en B B(V*, A*); }; struct D : A, B { virtual void f(); // D::f es el reemplazador final de V::f en D virtual void g(); // D::g es el reemplazador final de V::g en D // nota: A se inicializa antes que B D() : B((A*) this, this) {} }; // el constructor de B, llamado desde el constructor de D B::B(V* v, A* a) { f(); // llamada virtual a V::f (aunque D tiene el reemplazador final, D no existe) g(); // llamada virtual a B::g, que es el reemplazador final en B v->g(); // el tipo de v es V, que es base de B, la llamada virtual llama a B::g como antes a->f(); // el tipo de a es A, que no es una base de B. Pertenece a una rama diferente de la // jerarquía. Intentar una llamada virtual a través de esa rama causa // comportamiento indefinido aunque A ya estaba completamente construido en este // caso (se construyó antes que B ya que aparece antes que B en la lista // de las bases de D). En la práctica, se intentará la llamada virtual a A::f // usando la tabla de funciones miembro virtuales de B, ya que esa es // la que está activa durante la construcción de B) }
Palabras clave
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.
| DR | Se aplica a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 258 | C++98 |
una función miembro no-const de una clase derivada podría volverse
virtual debido a una función miembro virtual const de su base |
la virtualidad también requiere que las calificaciones
cv- sean las mismas |
| CWG 477 | C++98 | una declaración friend podría contener el virtual especificador | no permitido |
| CWG 1516 | C++98 |
no se proporcionaron las definiciones de los términos "llamada a función virtual"
y "llamada virtual" |
proporcionadas |
Véase también
| clases derivadas y modos de herencia | |
override
especificador
(C++11)
|
declara explícitamente que un método sobrescribe otro método |
final
especificador
(C++11)
|
declara que un método no puede ser sobrescrito o que una clase no puede ser derivada |