Namespaces
Variants

virtual function specifier

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Virtual function
override specifier (C++11)
final specifier (C++11)
Special member functions
Templates
Miscellaneous

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 override , pero no sobreescribe una función virtual, el programa está mal formado:

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 final , y otra función intenta sobreescribirla, el programa está mal formado:

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 consteval no debe anular ni ser anulada por una función virtual no- consteval .

(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 de Derived::f() .
  • el tipo de retorno de Derived::f() debe ser igual o menos cv-qualified que el tipo de retorno de Base::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

virtual

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