Buscar..


Sintaxis

  • vacío virtual f ();

  • vacío virtual g () = 0;

  • // C ++ 11 o posterior:

    • anulación virtual h (h);
    • anulación i () anulación;
    • vacío virtual j () final;
    • void k () final;

Observaciones

Usando override con virtual en C ++ 11 y versiones posteriores

La override especificador tiene un significado especial en C ++ 11 en adelante, si se adjunta al final de la firma de la función. Esto significa que una función es

  • Anulando la función presente en la clase base y
  • La función de clase base es virtual

No hay run time significado de run time de este especificador, ya que está pensado principalmente como una indicación para compiladores

El siguiente ejemplo demostrará el cambio en el comportamiento con nuestro uso sin anular.

Sin override :

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // Y::f() will not override X::f() because it has a different signature,
    // but the compiler will accept the code (and silently ignore Y::f()).
    virtual void f(int a) { std::cout << a << "\n"; }
};

Con override :

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // The compiler will alert you to the fact that Y::f() does not
    // actually override anything.
    virtual void f(int a) override { std::cout << a << "\n"; }
};

Tenga en cuenta que la override no es una palabra clave, sino un identificador especial que solo puede aparecer en las firmas de funciones. En todos los demás contextos, la override todavía se puede usar como un identificador:

void foo() {
    int override = 1; // OK.
    int virtual = 2;  // Compilation error: keywords can't be used as identifiers.
}

Funciones de miembro virtual vs no virtual

Con funciones de miembro virtual:

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // Specifying virtual again here is optional
    // because it can be inferred from X::f().
    virtual void f() { std::cout << "Y::f()\n"; } 
};

void call(X& a) {
    a.f();
}

int main() {
    X x;
    Y y;
    call(x); // outputs "X::f()"
    call(y); // outputs "Y::f()"
}

Sin funciones de miembro virtual:

#include <iostream>

struct X {
   void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
   void f() { std::cout << "Y::f()\n"; }
};

void call(X& a) {
    a.f();
}

int main() {
    X x;
    Y y;
    call(x); // outputs "X::f()"
    call(y); // outputs "X::f()"
}

Funciones virtuales finales

C ++ 11 introdujo el especificador final que prohíbe la invalidación de métodos si aparece en la firma del método:

class Base {
public:
    virtual void foo() {
        std::cout << "Base::Foo\n";
    }
};

class Derived1 : public Base {
public:
    // Overriding Base::foo
    void foo() final {
        std::cout << "Derived1::Foo\n";
    }
};

class Derived2 : public Derived1 {
public:
    // Compilation error: cannot override final method
    virtual void foo() {
        std::cout << "Derived2::Foo\n";
    }
};

El especificador final solo se puede utilizar con la función miembro "virtual" y no se puede aplicar a funciones miembro no virtuales

Como final , también hay un especificador de llamada 'reemplazo' que evita el reemplazo de funciones virtual en la clase derivada.

Los especificadores override y final pueden combinarse para tener el efecto deseado:

class Derived1 : public Base {
public:
    void foo() final override {
        std::cout << "Derived1::Foo\n";
    }
};

Comportamiento de funciones virtuales en constructores y destructores.

El comportamiento de las funciones virtuales en constructores y destructores a menudo es confuso cuando se encuentran por primera vez.

#include <iostream>
using namespace std;

class base { 
public:
    base() { f("base constructor"); }
    ~base() { f("base destructor"); }

    virtual const char* v() { return "base::v()"; }

    void f(const char* caller) { 
        cout << "When called from " << caller << ", "  << v() << " gets called.\n"; 
    }        
};

class derived : public base {
public:
    derived() { f("derived constructor"); }
    ~derived() { f("derived destructor"); }

    const char* v() override { return "derived::v()"; }

};

int main() {
     derived d;
}

Salida:

Cuando se llama desde el constructor base, se llama base :: v ().
Cuando se llama desde un constructor derivado, se deriva :: v ().
Cuando se llama desde un destructor derivado, se deriva :: v ().
Cuando se llama desde el destructor base, se llama base :: v ().

El razonamiento detrás de esto es que la clase derivada puede definir miembros adicionales que aún no están inicializados (en el caso del constructor) o que ya están destruidos (en el caso del destructor), y llamar a sus funciones miembro no sería seguro. Por lo tanto, durante la construcción y destrucción de objetos C ++, el tipo dinámico de *this se considera que es la clase del constructor o destructor y no una clase más derivada.

Ejemplo:

#include <iostream>
#include <memory>

using namespace std;
class base {
public:
    base()
    {
        std::cout << "foo is " << foo() << std::endl;
    }
    virtual int foo() { return 42; }
};

class derived : public base {
    unique_ptr<int> ptr_;
public:
    derived(int i) : ptr_(new int(i*i)) { }
    // The following cannot be called before derived::derived due to how C++ behaves, 
    // if it was possible... Kaboom!
    int foo() override   { return *ptr_; } 
};

int main() {
    derived d(4);
}

Funciones virtuales puras

También podemos especificar que una función virtual es simplemente virtual (abstracta), agregando = 0 a la declaración. Las clases con una o más funciones virtuales puras se consideran abstractas y no se pueden crear instancias; solo las clases derivadas que definen o heredan definiciones para todas las funciones virtuales puras pueden ser instanciadas.

struct Abstract {
    virtual void f() = 0;
};

struct Concrete {
    void f() override {}
};

Abstract a; // Error.
Concrete c; // Good.

Incluso si una función se especifica como virtual pura, se le puede dar una implementación predeterminada. A pesar de esto, la función todavía se considerará abstracta, y las clases derivadas tendrán que definirla antes de que puedan ser instanciadas. En este caso, la versión de la clase derivada de la función puede incluso llamar a la versión de la clase base.

struct DefaultAbstract {
    virtual void f() = 0;
};
void DefaultAbstract::f() {}

struct WhyWouldWeDoThis : DefaultAbstract {
    void f() override { DefaultAbstract::f(); }
};

Hay un par de razones por las que podríamos querer hacer esto:

  • Si queremos crear una clase que no pueda ser instanciada, pero que no evite que sus clases derivadas sean instanciadas, podemos declarar el destructor como simplemente virtual. Al ser el destructor, debe definirse de todos modos, si queremos poder desasignar la instancia. Y como es muy probable que el destructor ya sea virtual para evitar fugas de memoria durante el uso polimórfico , no incurriremos en un impacto de rendimiento innecesario al declarar otra función virtual . Esto puede ser útil al hacer interfaces.

      struct Interface {
          virtual ~Interface() = 0;
      };
      Interface::~Interface() = default;
    
      struct Implementation : Interface {};
      // ~Implementation() is automatically defined by the compiler if not explicitly
      //  specified, meeting the "must be defined before instantiation" requirement.
    
  • Si la mayoría o todas las implementaciones de la función virtual pura contendrán código duplicado, ese código se puede mover a la versión de clase base, haciendo que el código sea más fácil de mantener.

      class SharedBase {
          State my_state;
          std::unique_ptr<Helper> my_helper;
          // ...
    
        public:
          virtual void config(const Context& cont) = 0;
          // ...
      };
      /* virtual */ void SharedBase::config(const Context& cont) {
          my_helper = new Helper(my_state, cont.relevant_field);
          do_this();
          and_that();
      }
    
      class OneImplementation : public SharedBase {
          int i;
          // ...
    
        public:
          void config(const Context& cont) override;
          // ...
      };
      void OneImplementation::config(const Context& cont) /* override */ {
          my_state = { cont.some_field, cont.another_field, i };
          SharedBase::config(cont);
          my_unique_setup();
      };
    
      // And so on, for other classes derived from SharedBase.
    


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow