¿Por qué necesitamos funciones virtuales en C++?


Estoy aprendiendo C++ y solo estoy entrando en funciones virtuales.

Por lo que he leído (en el libro y en línea), las funciones virtuales son funciones en la clase base que puede reemplazar en clases derivadas.

Pero anteriormente en el libro, al aprender sobre la herencia básica, pude anular funciones base en clases derivadas sin usar virtual.

Entonces, ¿qué me estoy perdiendo aquí? Sé que hay más funciones virtuales, y parece ser importante, así que quiero ser claro en lo que es exactamente. No puedo encontrar una respuesta directa en línea.

Author: Peter Mortensen, 2010-03-06

22 answers

Aquí es cómo entendí no solo lo que virtual las funciones son, pero por qué se requieren:

Digamos que tienes estas dos clases: {[18]]}

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

En su función principal:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

Hasta ahora todo bien, ¿verdad? Los animales comen comida genérica, los gatos comen ratas, todo sin virtual.

Cambiémoslo un poco ahora para que eat() se llame a través de una función intermedia (una función trivial solo para este ejemplo):

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

Ahora nuestra función principal is:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

Uh oh... pasamos un gato en func(), pero no come ratas. En caso de sobrecarga func() por lo que se necesita un Cat*? Si tienes que derivar más animales de los animales, todos necesitarían sus propios func().

La solución es hacer de eat() de la clase Animal una función virtual:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

Principal:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

Hecho.

 2327
Author: M Perry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-12 16:36:42

Sin "virtual" se obtiene "early binding". Qué implementación del método se utiliza se decide en tiempo de compilación basado en el tipo del puntero que se llama a través.

Con "virtual" se obtiene "late binding". Qué implementación del método se utiliza se decide en tiempo de ejecución basado en el tipo del objeto apuntado-a-lo que fue construido originalmente como. Esto no es necesariamente lo que pensarías basado en el tipo de puntero que apunta a eso objeto.

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* obj = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

obj->Method1 ();  //  Prints "Base::Method1"
obj->Method2 ();  //  Prints "Derived::Method2"

EDITAR - ver esta pregunta.

También - este tutorial cubre el enlace temprano y tardío en C++.

 553
Author: Steve314,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:47

Se necesita al menos 1 nivel de herencia y un downcast para demostrarlo. He aquí un ejemplo muy simple:

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    cout << d->Says();   // always Woof
    cout << a->Says();   // Woof or ?, depends on virtual
}
 75
Author: Henk Holterman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-02 17:01:50

Se necesita métodos virtuales para seguro downcasting, la simplicidad y concisión.

Eso es lo que hacen los métodos virtuales: se degradan de forma segura, con un código aparentemente simple y conciso, evitando los moldes manuales inseguros en el código más complejo y detallado que de otra manera tendría.


Método no virtual ⇒ enlace estático

El siguiente código es intencionalmente "incorrecto". No declara el método value como virtual, y por lo tanto produce un resultado "incorrecto" no deseado, a saber, 0:

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

En la línea comentada como "mala" se llama al método Expression::value, porque el tipo conocido estáticamente (el tipo conocido en tiempo de compilación) es Expression, y el método value no es virtual.


Método virtual ⇒ enlace dinámico.

Declarando value como virtual en el tipo estáticamente conocido Expression se asegura de que cada llamada compruebe qué tipo real de objeto es, y llamar a la implementación relevante de value para ese tipo dinámico :

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Aquí la salida es 6.86 como debería ser, ya que el método virtual es llamado virtualmente. Esto también se llama enlace dinámico de las llamadas. Se realiza una pequeña comprobación, encontrando el tipo dinámico real del objeto, y se llama a la implementación del método relevante para ese tipo dinámico.

La implementación relevante es la más específica (la más derived) class.

Tenga en cuenta que las implementaciones de métodos en clases derivadas aquí no están marcadas virtual, sino que están marcadas override. Se pueden marcar virtual pero son automáticamente virtuales. La palabra clave overrideasegura que si hay no tal método virtual en alguna clase base, entonces obtendrá un error (que es deseable).


, La fealdad de hacer esto sin métodos virtuales

Sin virtual uno tendría que implementa alguna versión Do It Yourself del enlace dinámico. Es esto lo que generalmente implica downcasting manual inseguro, complejidad y verbosidad.

Para el caso de una sola función, como aquí, es suficiente almacenar un puntero de función en el objeto y llamar a través de ese puntero de función, pero aún así implica algunos downcasts inseguros, complejidad y verbosidad, a saber:

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Una forma positiva de ver esto es, si se encuentra con una degradación insegura, la complejidad y la verbosidad como la anterior, a menudo un método virtual o métodos realmente pueden ayudar.

 33
Author: Cheers and hth. - Alf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-24 07:24:45

Si la clase base es Base, y una clase derivada es Der, puede tener un puntero Base *p que en realidad apunta a una instancia de Der. Cuando llamas a p->foo();, si foo es no virtual, entonces la versión de Base se ejecuta, ignorando el hecho de que p realmente apunta a un Der. Si foo es virtual, p->foo() ejecuta la anulación "leafmost" de foo, teniendo plenamente en cuenta la clase real del elemento apuntado. Así que la diferencia entre virtual y no virtual es en realidad bastante crucial: el primero permite el polimorfismo en tiempo de ejecución , el concepto central de la programación OO, mientras que el segundo no.

 29
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-06 16:56:57

Las funciones virtuales se utilizan para soportar el Polimorfismo en tiempo de ejecución.

Es decir, la palabra clave virtual le dice al compilador que no tome la decisión (de enlace de función) en tiempo de compilación, sino que la posponga para el tiempo de ejecución".

  • Puede hacer virtual una función precediendo la palabra clave virtual en su declaración de clase base. Por ejemplo,

     class Base
     {
        virtual void func();
     }
    
  • Cuando una Clase Base tiene una función miembro virtual, cualquier clase que hereda desde la Clase Base puede redefinir la función con exactamente el mismo prototipo es decir, solo se puede redefinir la funcionalidad, no la interfaz de la función.

     class Derive : public Base
     {
        void func();
     }
    
  • Un puntero de clase base se puede usar para apuntar a un objeto de clase base, así como a un objeto de clase Derivado.

  • Cuando se llama a la función virtual usando un puntero de clase base, el compilador decide en tiempo de ejecución qué versión de la función, es decir, la versión de la clase base o la overridden Derived class version - se llama. Esto se llama Polimorfismo en tiempo de ejecución.
 28
Author: KIN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-25 19:53:24

Se explica la necesidad de una función Virtual [Fácil de entender]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

La salida será:

Hello from Class A.

Pero con función virtual:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

La salida será:

Hello from Class B.

Por lo tanto, con la función virtual puede lograr polimorfismo en tiempo de ejecución.

 26
Author: Ajay GU,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-12 11:56:42

Hay que distinguir entre sobreescritura y sobrecarga. Sin la palabra clave virtual solo se sobrecarga un método de una clase base. Esto no significa nada más que esconderse. Digamos que tienes una clase base Base y una clase derivada Specialized que ambas implementan void foo(). Ahora tiene un puntero a Base apuntando a una instancia de Specialized. Cuando se llama foo() en él se puede observar la diferencia que hace virtual: Si el método es virtual, se utilizará la implementación de Specialized, si falta, el se elegirá la versión de Base. Es una buena práctica nunca sobrecargar los métodos de una clase base. Hacer que un método no sea virtual es la forma en que su autor le dice que su extensión en subclases no está destinada.

 21
Author: h0b0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-06 07:27:04

Me gustaría agregar otro uso de la función Virtual, aunque utiliza el mismo concepto que las respuestas mencionadas anteriormente, pero supongo que vale la pena mencionarlo.

DESTRUCTOR VIRTUAL

Considere este programa a continuación, sin declarar la clase Base destructor como virtual; la memoria para Cat puede no ser limpiada.

class Animal {
    public:
    ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat() {
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Salida:

Deleting an Animal
class Animal {
    public:
    virtual ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat(){
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Salida:

Deleting an Animal name Cat
Deleting an Animal
 19
Author: Aryaman Gupta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-12 16:39:59

¿Por qué necesitamos Métodos Virtuales en C++?

Respuesta rápida:

  1. Nos proporciona uno de los "ingredientes necesarios"1 para programación orientada a objetos.

En Bjarne Stroustrup Programación en C++: Principios y Práctica, (14.3):

La función virtual proporciona la capacidad de definir una función en una clase base y tener una función del mismo nombre y tipo en una clase derivada llamada cuando un usuario llama a la base función de clase. Eso a menudo se llama polimorfismo en tiempo de ejecución, dynamic dispatch , o run-time dispatch porque la función llamada se determina en tiempo de ejecución en función del tipo del objeto utilizado.

  1. Es la implementación más rápida y eficiente si necesita una llamada a una función virtual 2.

Para manejar una llamada virtual, se necesita uno o más datos relacionados con el objeto derivado 3. La manera que se hace generalmente es agregar la dirección de la tabla de funciones. Esta tabla se conoce generalmente como virtual table o virtual function table y su dirección a menudo se llama virtual pointer . Cada función virtual obtiene una ranura en la tabla virtual. Dependiendo del tipo de objeto (derivado) del llamante, la función virtual, a su vez, invoca la anulación respectiva.


1.El uso de la herencia, tiempo de ejecución polimorfismo y encapsulación es la definición más común de programación orientada a objetos.

2. No puede codificar la funcionalidad para ser más rápido o para usar menos memoria usando otras características de lenguaje para seleccionar entre alternativas en tiempo de ejecución. Bjarne Stroustrup C++ Programming: Principles and Practice (en inglés).(14.3.1).

3. Algo para decir qué función es realmente invocada cuando llamamos a la clase base que contiene la función virtual.

 16
Author: Ziezi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-27 09:37:47

Cuando tienes una función en la clase base, puedes Redefine o Override en la clase derivada.

Redefiniendo un método : Una nueva implementación para el método de la clase base se da en la clase derivada. No facilita Dynamic binding.

Reemplazar un método: Redefining a virtual method de la clase base en la clase derivada. El método virtual facilita el Enlace dinámico.

Así que cuando dijiste:

Pero antes en el libro, al aprender sobre la herencia básica, estaba capaz de anular métodos base en clases derivadas sin usar "virtual".

No lo estaba anulando ya que el método en la clase base no era virtual, sino que lo estaba redefiniendo

 12
Author: nitin_cherian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-06 08:29:56

Tengo mi respuesta en forma de una conversación para ser una mejor lectura:


¿Por qué necesitamos funciones virtuales?

Debido al polimorfismo.

¿Qué es el Polimorfismo?

El hecho de que un puntero base también puede apuntar a objetos de tipo derivado.

¿Cómo conduce esta definición de polimorfismo a la necesidad de funciones virtuales?

Bien, a través de la unión temprana.

Lo que es temprano vinculante?

El enlace temprano(enlace en tiempo de compilación) en C++ significa que una llamada a una función se arregla antes de que se ejecute el programa.

Entonces...?

Así que si usas un tipo base como parámetro de una función, el compilador solo reconocerá la interfaz base, y si llamas a esa función con cualquier argumento de clases derivadas, se corta, que no es lo que quieres que suceda.

Si no es lo que queremos que suceda, ¿por qué es esto ¿permitido?

Porque necesitamos Polimorfismo!

¿Cuál es el beneficio del polimorfismo entonces?

Puede usar un puntero de tipo base como el parámetro de una sola función, y luego en el tiempo de ejecución de su programa, puede acceder a cada una de las interfaces de tipo derivadas(por ejemplo, sus funciones miembro) sin ningún problema, utilizando la desreferenciación de ese puntero base único.

Todavía no sé para qué sirven las funciones virtuales...! Y esta fue mi primera pregunta!

¡Bueno, esto es porque hiciste tu pregunta demasiado pronto!

¿Por qué necesitamos funciones virtuales?

Supongamos que ha llamado a una función con un puntero base, que tenía la dirección de un objeto de una de sus clases derivadas. Como hemos hablado de ello anteriormente, en el tiempo de ejecución, este puntero se desreferenció, hasta ahora tan bueno, sin embargo, esperamos que se ejecute un método(== una función miembro) "de nuestra clase derivada"! Sin embargo, un mismo método (uno que tiene un mismo encabezado) ya está definido en la clase base, así que ¿por qué debería su programa molestarse en elegir el otro método? En otras palabras, quiero decir, ¿cómo puedes distinguir este escenario de lo que solíamos ver antes?

La respuesta breve es "una función miembro virtual en base", y una respuesta un poco más larga es que, "en este paso, si el programa ve una función virtual en la clase base, sabe (se da cuenta) que está tratando de usar polimorfismo" y así va a clases derivadas (usando v-table, una forma de enlace tardío) para encontrar que otro método con la misma cabecera, pero con-esperadamente - una implementación diferente.

¿Por qué una implementación diferente?

¡Cabeza de nudillo! ¡Ve a leer un buen libro!

OK, espera espera espera, ¿por qué uno se molestaría en usar punteros base, cuando él / ella podría simplemente usar punteros de tipo derivado? Tú eres el juez, ¿vale la pena todo este dolor de cabeza? Mira estos dos fragmentos de código:

//1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

//2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

OK, aunque creo que 1 es aún mejor que 2, podría escribir 1 como esto:

//1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

Y además, debes ser consciente de que esto es solo un uso artificial de todas las cosas que te he explicado hasta ahora. En lugar de esto, supongamos, por ejemplo, una situación en la que tiene una función en su programa que utiliza los métodos de cada uno de las clases derivadas, respectivamente(getMonthBenefit()):

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

Ahora, trate de volver a escribir esto, sin ningún dolor de cabeza!

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

Y en realidad, esto podría ser todavía un ejemplo artificial tampoco!

 11
Author: M-J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-12 16:38:26

Ayuda si conoces los mecanismos subyacentes. C++ formaliza algunas técnicas de codificación utilizadas por los programadores de C, las "clases" reemplazadas por "superposiciones": las estructuras con secciones de encabezado comunes se usarían para manejar objetos de diferentes tipos pero con algunos datos u operaciones comunes. Normalmente, la estructura base de la superposición (la parte común) tiene un puntero a una tabla de funciones que apunta a un conjunto diferente de rutinas para cada tipo de objeto. C++ hace lo mismo, pero oculta los mecanismos, es decir, el C++ ptr->func(...) donde func es virtual como C sería (*ptr->func_table[func_num])(ptr,...), donde lo que cambia entre clases derivadas es el contenido func_table. [Un método no virtual ptr - >func() solo se traduce a mangled_func (ptr,..).]

El resultado de esto es que solo necesita entender la clase base para llamar a los métodos de una clase derivada, es decir, si una rutina entiende la clase A, puede pasarle un puntero de clase B derivado, entonces los métodos virtuales llamados serán los de B en lugar de A, ya que pasa por la función tabla B apunta a.

 10
Author: Kev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-30 08:43:26

La palabra clave virtual le dice al compilador que no debe realizar un enlace temprano. En su lugar, debe instalar automáticamente todos los mecanismos necesarios para realizar el enlace tardío. Para lograr esto, el compiler1 típico crea una sola tabla (llamada VTABLE) para cada clase que contiene funciones virtuales.El compilador coloca las direcciones de las funciones virtuales para esa clase en particular en la VTABLE. En cada clase con funciones virtuales,coloca secretamente un puntero, llamado vpointer (abreviado como VPTR), que apunta a la tabla V para ese objeto. Cuando realiza una llamada a una función virtual a través de un puntero de clase base, el compilador inserta silenciosamente código para obtener el VPTR y buscar la dirección de la función en la VTABLE, llamando así a la función correcta y causando que se produzca el enlace tardío.

Más detalles en este enlace http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html

 7
Author: rvkreddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-23 12:49:31

La palabra clave virtual obliga al compilador a elegir la implementación del método definida en la clase del objeto en lugar de en la clase del puntero.

Shape *shape = new Triangle(); 
cout << shape->getName();

En el ejemplo anterior, Shape::getName será llamado por defecto, a menos que getName() esté definido como virtual en la clase Base Shape. Esto obliga al compilador a buscar la implementación de getName() en la clase Triangle en lugar de en la clase Shape.

La tabla virtual es la mecanismo en el que el compilador realiza un seguimiento de las diversas implementaciones de métodos virtuales de las subclases. Esto también se llama despacho dinámico, y hay algunos gastos generales asociados con él.

Finalmente, ¿por qué se necesita virtual en C++, por qué no hacerlo el comportamiento predeterminado como en Java?

  1. C++ se basa en los principios de "Cero Gastos generales" y "Paga por lo que usas". Por lo tanto, no intenta realizar un envío dinámico para usted, a menos que lo necesite.
  2. Para proporcionar más control a la interfaz. Al hacer que una función no sea virtual, la clase interface / abstract puede controlar el comportamiento en todas sus implementaciones.
 6
Author: javaProgrammer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-07 17:59:06

¿Por qué necesitamos funciones virtuales?

Las funciones virtuales evitan un problema innecesario de encasillamiento, y algunos de nosotros podemos debatir por qué necesitamos funciones virtuales cuando podemos usar puntero de clase derivada para llamar a la función específica en la clase derivada!la respuesta es - anula toda la idea de herencia en el desarrollo de sistemas grandes, donde tener un único objeto de clase base de puntero es muy deseado.

Vamos a comparar a continuación dos programas simples para entender la importancia de funciones virtuales:

Programa sin funciones virtuales:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

SALIDA:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

Programa con función virtual:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

SALIDA:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

Analizando de cerca ambas salidas se puede entender la importancia de las funciones virtuales.

 4
Author: akshaypmurgod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-11 12:07:16

Acerca de la eficiencia, las funciones virtuales son ligeramente menos eficientes que las funciones de enlace temprano.

"Este mecanismo de llamada virtual se puede hacer casi tan eficiente como el mecanismo de "llamada a función normal" (dentro del 25%). Su espacio superior es un puntero en cada objeto de una clase con funciones virtuales más un vtbl para cada clase " [A tour of C++ by Bjarne Stroustrup]

 2
Author: Duke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-28 14:19:30

Los métodos virtuales se utilizan en el diseño de interfaces. Por ejemplo, en Windows hay una interfaz llamada IUnknown como a continuación:

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

Estos métodos se dejan al usuario de la interfaz para implementar. Son esenciales para la creación y destrucción de ciertos objetos que deben heredar IUnknown. En este caso, el tiempo de ejecución es consciente de los tres métodos y espera que se implementen cuando los llame. Así que, en cierto sentido, actúan como un contrato entre el objeto en sí y lo que utiliza ese objeto.

 2
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-20 03:01:32

Aquí hay un ejemplo completo que ilustra por qué se usa el método virtual.

#include <iostream>

using namespace std;

class Basic
{
    public:
    virtual void Test1()
    {
        cout << "Test1 from Basic." << endl;
    }
    virtual ~Basic(){};
};
class VariantA : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantA." << endl;
    }
};
class VariantB : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantB." << endl;
    }
};

int main()
{
    Basic *object;
    VariantA *vobjectA = new VariantA();
    VariantB *vobjectB = new VariantB();

    object=(Basic *) vobjectA;
    object->Test1();

    object=(Basic *) vobjectB;
    object->Test1();

    delete vobjectA;
    delete vobjectB;
    return 0;
}
 1
Author: user3371350,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-02 10:29:55

Aquí hay una versión combinada del código C++ para las dos primeras respuestas.

#include        <iostream>
#include        <string>

using   namespace       std;

class   Animal
{
        public:
#ifdef  VIRTUAL
                virtual string  says()  {       return  "??";   }
#else
                string  says()  {       return  "??";   }
#endif
};

class   Dog:    public Animal
{
        public:
                string  says()  {       return  "woof"; }
};

string  func(Animal *a)
{
        return  a->says();
}

int     main()
{
        Animal  *a = new Animal();
        Dog     *d = new Dog();
        Animal  *ad = d;

        cout << "Animal a says\t\t" << a->says() << endl;
        cout << "Dog d says\t\t" << d->says() << endl;
        cout << "Animal dog ad says\t" << ad->says() << endl;

        cout << "func(a) :\t\t" <<      func(a) <<      endl;
        cout << "func(d) :\t\t" <<      func(d) <<      endl;
        cout << "func(ad):\t\t" <<      func(ad)<<      endl;
}

Dos resultados diferentes son:

Sin # define virtual , se enlaza en tiempo de compilación. Animal * ad y func (Animal *) apuntan al método says() del Animal.

$ g++ virtual.cpp -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  ??
func(a) :       ??
func(d) :       ??
func(ad):       ??

Con # define virtual , se enlaza en tiempo de ejecución. Dog * d, Animal *ad y func (Animal *) apuntan/se refieren al método says () del Perro ya que Dog es su tipo de objeto. A menos que el método [Dog's says () "woof"] no esté definido, será la que se busque primero en el árbol de clases, es decir, las clases derivadas pueden anular los métodos de sus clases base [Animal's says()].

$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

Es interesante notar que todos los atributos de clase (datos y métodos) en Python son efectivamente virtuales. Dado que todos los objetos se crean dinámicamente en tiempo de ejecución, no hay declaración de tipo ni necesidad de palabra clave virtual. A continuación se muestra la versión de código de Python:

class   Animal:
        def     says(self):
                return  "??"

class   Dog(Animal):
        def     says(self):
                return  "woof"

def     func(a):
        return  a.says()

if      __name__ == "__main__":

        a = Animal()
        d = Dog()
        ad = d  #       dynamic typing by assignment

        print("Animal a says\t\t{}".format(a.says()))
        print("Dog d says\t\t{}".format(d.says()))
        print("Animal dog ad says\t{}".format(ad.says()))

        print("func(a) :\t\t{}".format(func(a)))
        print("func(d) :\t\t{}".format(func(d)))
        print("func(ad):\t\t{}".format(func(ad)))

La salida es:

Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

Que es idéntica a Virtual define de C++. Tenga en cuenta que d y ad son dos variables de puntero diferentes que se refieren / apuntan a la misma instancia de Perro. La expresión (ad es d) devuelve True y sus valores son los mismos main.Objeto perro en 0xb79f72cc>.

 1
Author: Leon Chang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-05 23:12:13

Necesitamos métodos virtuales para soportar el "Polimorfismo en tiempo de ejecución". Cuando hace referencia a un objeto de clase derivada mediante un puntero o una referencia a la clase base, puede llamar a una función virtual para ese objeto y ejecutar la versión de la función de la clase derivada.

 0
Author: rashedcs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-07 03:23:26

Creo que se está refiriendo al hecho de que una vez que un método se declara virtual, no necesita usar la palabra clave 'virtual' en las anulaciones.

class Base { virtual void foo(); };

class Derived : Base 
{ 
  void foo(); // this is overriding Base::foo
};

Si no usas 'virtual' en la declaración foo de Base, entonces el foo de Derived simplemente lo estaría sombreando.

 0
Author: edwinc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-19 20:15:55