Virtual / puro virtual explicado

346

¿Qué significa exactamente si una función se define como virtual y es lo mismo que virtual puro?

Justin
fuente

Respuestas:

339

De la función virtual de Wikipedia ...

En la programación orientada a objetos, en lenguajes como C ++ y Object Pascal, una función virtual o método virtual es una función o método heredable y reemplazable para el cual se facilita el despacho dinámico. Este concepto es una parte importante de la porción de polimorfismo (tiempo de ejecución) de la programación orientada a objetos (OOP). En resumen, una función virtual define una función objetivo que se ejecutará, pero el objetivo puede no ser conocido en el momento de la compilación.

A diferencia de una función no virtual, cuando se anula una función virtual, la versión más derivada se usa en todos los niveles de la jerarquía de clases, en lugar de solo el nivel en el que se creó. Por lo tanto, si un método de la clase base llama a un método virtual, se utilizará la versión definida en la clase derivada en lugar de la versión definida en la clase base.

Esto contrasta con las funciones no virtuales, que aún pueden anularse en una clase derivada, pero la versión "nueva" solo será utilizada por la clase derivada y por debajo, pero no cambiará en absoluto la funcionalidad de la clase base.

mientras..

Una función virtual pura o un método virtual puro es una función virtual que una clase derivada debe implementar si la clase derivada no es abstracta.

Cuando existe un método virtual puro, la clase es "abstracta" y no se puede instanciar por sí sola. En su lugar, se debe utilizar una clase derivada que implemente los métodos virtuales puros. Un virtual puro no está definido en la clase base en absoluto, por lo que una clase derivada debe definirlo, o esa clase derivada también es abstracta y no puede ser instanciada. Solo se puede crear una instancia de una clase que no tenga métodos abstractos.

Un virtual proporciona una forma de anular la funcionalidad de la clase base, y un virtual puro lo requiere .

Diego Dias
fuente
10
Entonces ... ¿es puramente virtual una palabra clave, o simplemente un término que se usa?
Justin
197
Función virtual vacía () = 0; Es un puro virtual. El "= 0" indica es pureza.
Goz
8
Justin, 'puro virtual' es solo un término (no una palabra clave, mira mi respuesta a continuación) usado para significar "esta función no puede ser implementada por la clase base. Como dijo Goz, agregando el" = 0 "al final de un virtual función lo hace "puro"
Nick Haddad
14
Creo que Stroustrup dijo que quería agregar una purepalabra clave, pero que Bell Labs estaba a punto de hacer un lanzamiento importante de C ++, y su gerente no lo permitiría en esa etapa tardía. Agregar palabras clave es un gran problema.
quark
14
Esta no es una buena respuesta. Se puede anular cualquier método, no solo los virtuales. Vea mi respuesta para más detalles.
Asik
212

Me gustaría comentar sobre la definición de Wikipedia de virtual, como lo repiten varios aquí. [En el momento en que se escribió esta respuesta,] Wikipedia definió un método virtual como uno que se puede anular en subclases. [Afortunadamente, Wikipedia se ha editado desde entonces, y ahora lo explica correctamente.] Eso es incorrecto: cualquier método, no solo los virtuales, puede anularse en subclases. Lo que hace virtual es darle polimorfismo, es decir, la capacidad de seleccionar en tiempo de ejecución la anulación más derivada de un método .

Considere el siguiente código:

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

¿Cuál es la salida de este programa?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

Derivado anula todos los métodos de Base: no solo el virtual, sino también el no virtual.

Vemos que cuando tiene un puntero base a derivado (bDerived), llamar a NonVirtual llama a la implementación de la clase Base. Esto se resuelve en tiempo de compilación: el compilador ve que bDerived es una Base *, que NonVirtual no es virtual, por lo que hace la resolución en la clase Base.

Sin embargo, llamar a Virtual llama a la implementación de la clase Derivada. Debido a la palabra clave virtual, la selección del método ocurre en tiempo de ejecución , no en tiempo de compilación. Lo que sucede aquí en tiempo de compilación es que el compilador ve que se trata de una Base *, y que está llamando a un método virtual, por lo que inserta una llamada a la tabla vtable en lugar de la clase Base. Esta vtable se instancia en tiempo de ejecución, de ahí la resolución del tiempo de ejecución a la anulación más derivada.

Espero que esto no haya sido demasiado confuso. En resumen, cualquier método puede anularse, pero solo los métodos virtuales le dan polimorfismo, es decir, la selección en tiempo de ejecución de la anulación más derivada. En la práctica, sin embargo, anular un método no virtual se considera una mala práctica y rara vez se usa, por lo que muchas personas (incluido quien escribió ese artículo de Wikipedia) piensan que solo los métodos virtuales pueden anularse.

Asik
fuente
66
El hecho de que el artículo de Wikipedia (que no defiendo) define un método virtual "como uno que se puede anular en subclases" no excluye la posibilidad de que se puedan declarar otros métodos no virtuales con el mismo nombre. Esto se conoce como sobrecarga.
26
Sin embargo, la definición es incorrecta. Un método que se puede anular en una clase derivada no es virtual por definición; si el método puede ser anulado es irrelevante para la definición de "virtual". Además, "sobrecarga" generalmente se refiere a tener múltiples métodos con el mismo nombre y tipo de retorno pero diferentes argumentos, en la misma clase; es muy diferente de "anular", lo que implica exactamente la misma firma pero en una clase derivada. Cuando se hace de forma no polimórfica (base no virtual), a menudo se le llama "ocultación".
Asik
55
Esta debería ser la respuesta aceptada. Ese artículo particular de Wikipedia que tomaré el tiempo para vincular aquí ya que nadie más en esta pregunta lo ha hecho , es una basura completa. +1, buen señor.
josaphatv
2
Ahora tiene sentido. Gracias, buen señor, por explicar adecuadamente que cualquier método puede ser anulado por clases derivadas y el cambio está en cómo se comportará el compilador para elegir qué función se llama en diferentes situaciones.
Doodad
3
Puede ser útil agregar un Derived*con las mismas llamadas de función para conducir el punto a casa. De lo contrario, gran respuesta
Jeff Jones el
114

La palabra clave virtual le da a C ++ su capacidad de soportar el polimorfismo. Cuando tiene un puntero a un objeto de alguna clase como:

class Animal
{
  public:
    virtual int GetNumberOfLegs() = 0;
};

class Duck : public Animal
{
  public:
     int GetNumberOfLegs() { return 2; }
};

class Horse : public Animal
{
  public:
     int GetNumberOfLegs() { return 4; }
};

void SomeFunction(Animal * pAnimal)
{
  cout << pAnimal->GetNumberOfLegs();
}

En este ejemplo (tonto), la función GetNumberOfLegs () devuelve el número apropiado en función de la clase del objeto para el que se solicita.

Ahora, considere la función 'SomeFunction'. No le importa qué tipo de objeto animal se le pase, siempre y cuando se derive de Animal. El compilador lanzará automáticamente cualquier clase derivada de un animal a un animal, ya que es una clase base.

Si hacemos esto:

Duck d;
SomeFunction(&d);

produciría '2'. Si hacemos esto:

Horse h;
SomeFunction(&h);

produciría '4'. No podemos hacer esto:

Animal a;
SomeFunction(&a);

porque no se compilará debido a que la función virtual GetNumberOfLegs () es pura, lo que significa que debe implementarse derivando clases (subclases).

Las funciones virtuales puras se utilizan principalmente para definir:

a) clases abstractas

Estas son clases base donde debe derivar de ellas y luego implementar las funciones virtuales puras.

b) interfaces

Estas son clases 'vacías' donde todas las funciones son puramente virtuales y, por lo tanto, debe derivar y luego implementar todas las funciones.

JBRWilkinson
fuente
En su ejemplo, no puede hacer el # 4 porque no proporcionó una implementación del método virtual puro. No es estrictamente porque el método es puramente virtual.
iheanyi
@iheanyi No puede proporcionar la implementación al método virtual puro en la clase base. Por lo tanto, el caso 4 sigue siendo un error.
prasad
32

En una clase C ++, virtual es la palabra clave que designa que, un método puede ser anulado (es decir, implementado por) una subclase. Por ejemplo:

class Shape 
{
  public:
    Shape();
    virtual ~Shape();

    std::string getName() // not overridable
    {
      return m_name;
    }

    void setName( const std::string& name ) // not overridable
    {
      m_name = name;
    }

  protected:
    virtual void initShape() // overridable
    {
      setName("Generic Shape");
    }

  private:
    std::string m_name;
};

En este caso, una subclase puede anular la función initShape para realizar un trabajo especializado:

class Square : public Shape
{
  public: 
    Square();
    virtual ~Square();

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}

El término virtual puro se refiere a funciones virtuales que deben ser implementadas por una subclase y que la clase base no ha implementado. Usted designa un método como virtual puro utilizando la palabra clave virtual y agregando a = 0 al final de la declaración del método.

Entonces, si quisieras hacer Shape :: initShape puramente virtual, harías lo siguiente:

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};

Al agregar un método virtual puro a su clase, la convierte en una clase base abstracta que es muy útil para separar las interfaces de la implementación.

Nick Haddad
fuente
1
Con respecto a las "funciones virtuales que deben ser implementadas por una subclase", eso no es estrictamente cierto, pero la subclase también es abstracta si no lo son. Y las clases abstractas no pueden ser instanciadas. Además, "no puede ser implementado por la clase base" parece engañoso; Sugeriría que "no ha sido" sería mejor ya que no hay restricción a las modificaciones del código para agregar una implementación dentro de la clase base.
NVRAM
2
Y "la función getName no puede ser implementada por una subclase" no está del todo bien. Las subclases pueden implementar el método (con la misma o diferente firma) pero esa implementación no ANULARÁ el método. Puede implementar Circle como una subclase e implementar "std :: string Circle :: getName ()", luego puede llamar a cualquier método para una instancia de Circle. Pero si se usa a través de un puntero o referencia Shape, el compilador llamaría Shape :: getName ().
NVRAM
1
Buenos puntos en ambos frentes. Estaba tratando de evitar hablar de casos especiales para este ejemplo, modificaré la respuesta para que sea más indulgente. ¡Gracias!
Nick Haddad
@NickHaddad Hilo antiguo, pero preguntándose por qué llamó a su variable m_name. ¿Qué significa m_eso?
Tqn
1
@Tqn, suponiendo que NickHaddad haya seguido las convenciones, m_name es una convención de nomenclatura comúnmente llamada notación húngara. La m indica miembro de una estructura / clase, entero.
Ketcomp
16

"Virtual" significa que el método puede ser anulado en subclases, pero tiene una implementación directamente invocable en la clase base. "Virtual puro" significa que es un método virtual sin implementación directamente invocable. Dicho método debe anularse al menos una vez en la jerarquía de herencia: si una clase tiene algún método virtual no implementado, los objetos de esa clase no se pueden construir y la compilación fallará.

@quark señala que los métodos virtuales puros pueden tener una implementación, pero como los métodos virtuales puros deben anularse, la implementación predeterminada no se puede llamar directamente. Aquí hay un ejemplo de un método virtual puro con un valor predeterminado:

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

Según los comentarios, si la compilación fallará o no es específica del compilador. En GCC 4.3.3 al menos, no compilará:

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

Salida:

$ g++ -c virt.cpp 
virt.cpp: In function int main()’:
virt.cpp:8: error: cannot declare variable a to be of abstract type A
virt.cpp:1: note:   because the following virtual functions are pure within A’:
virt.cpp:3: note:   virtual void A::Hello()
John Millikin
fuente
debe anularse si desea crear una instancia de la clase. Si no crea ninguna instancia, el código se compilará bien.
Glen
1
La compilación no fallará. Si no hay implementación de un método virtual (puro), entonces esa clase / objeto no se puede instanciar. Puede que no ENLACE, pero se compilará.
Tim
@Glen, @tim: ¿en qué compilador? Cuando intento compilar un programa que crea una clase abstracta, no se compila.
John Millikin
@John Compilation solo fallará si intenta crear una instancia de una clase que contenga un PVF. Por supuesto, puede crear instancias de puntero o valores de referencia para tales clases.
55
Además, John, lo siguiente no está del todo bien: "'Virtual puro' significa que es un método virtual sin implementación". Los métodos virtuales puros pueden tener implementaciones. Pero no puede llamarlos directamente: debe anular y usar la implementación de la clase base desde la subclase. Esto le permite proporcionar una parte predeterminada de la implementación. Sin embargo, no es una técnica común.
quark
9

¿Cómo funciona la palabra clave virtual?

Suponga que el hombre es una clase base, el indio se deriva del hombre.

Class Man
{
 public: 
   virtual void do_work()
   {}
}

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}

Declarar do_work () como virtual simplemente significa: qué do_work () llamar se determinará SOLO en tiempo de ejecución.

Supongamos que sí

Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.

Si no se usa virtual, el compilador determina estáticamente o está vinculado estáticamente, dependiendo de qué objeto está llamando. Entonces, si un objeto de Man llama a do_work (), el do_work () de Man se llama INCLUSO AUNQUE SEÑALA A UN OBJETO INDIO

Creo que la respuesta más votada es engañosa: cualquier método, sea virtual o no, puede tener una implementación anulada en la clase derivada. Con referencia específica a C ++, la diferencia correcta es el enlace en tiempo de ejecución (cuando se usa virtual) y el tiempo de compilación (cuando no se usa virtual pero se anula un método y un puntero base apunta a un objeto derivado) enlace de funciones asociadas.

Parece haber otro comentario engañoso que dice:

"Justin, 'puro virtual' es solo un término (no una palabra clave, vea mi respuesta a continuación) usado para significar" esta clase básica no puede implementar esta función ".

¡ESTO ESTÁ MAL! ¡Las funciones puramente virtuales también pueden tener un cuerpo Y PUEDEN SER IMPLEMENTADAS! ¡La verdad es que una función virtual pura de una clase abstracta se puede llamar estáticamente! Dos autores muy buenos son Bjarne Stroustrup y Stan Lippman ... porque escribieron el idioma.

McMurdo
fuente
2
Desafortunadamente, una vez que una respuesta comienza a ser votada, todos los demás serán ignorados. Incluso aunque podrían ser mejores.
LtWorf
3

Una función virtual es una función miembro que se declara en una clase base y que se redefine por clase derivada. Las funciones virtuales son jerárquicas en orden de herencia. Cuando una clase derivada no anula una función virtual, se utiliza la función definida dentro de su clase base.

Una función virtual pura es aquella que no contiene ninguna definición relativa a la clase base. No tiene implementación en la clase base. Cualquier clase derivada debe anular esta función.

rashedcs
fuente
2

Simula, C ++ y C #, que utilizan el enlace de método estático de forma predeterminada, el programador puede especificar que determinados métodos deben usar el enlace dinámico etiquetándolos como virtuales. El enlace dinámico de métodos es fundamental para la programación orientada a objetos.

La programación orientada a objetos requiere tres conceptos fundamentales: encapsulación, herencia y enlace dinámico de métodos.

La encapsulación permite ocultar los detalles de implementación de una abstracción detrás de una interfaz simple.

La herencia permite que una nueva abstracción se defina como una extensión o refinamiento de alguna abstracción existente, obteniendo algunas o todas sus características automáticamente.

El enlace de método dinámico permite que la nueva abstracción muestre su nuevo comportamiento incluso cuando se usa en un contexto que espera la antigua abstracción.

PJT
fuente
1

Los métodos virtuales PUEDEN anularse derivando clases, pero necesitan una implementación en la clase base (la que se anulará)

Los métodos virtuales puros no tienen implementación de la clase base. Deben definirse por clases derivadas. (Por lo tanto, anular técnicamente no es el término correcto, porque no hay nada que anular).

Virtual corresponde al comportamiento predeterminado de Java, cuando la clase derivada anula un método de la clase base.

Los métodos virtuales puros corresponden al comportamiento de los métodos abstractos dentro de las clases abstractas. Y una clase que solo contiene constantes y métodos virtuales puros sería el colgante cpp de una interfaz.

johannes_lalala
fuente
0

Función virtual pura

prueba este código

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()=0;

};

class anotherClass:aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"hellow World";
    }

};
int main()
{
    //aClassWithPureVirtualFunction virtualObject;
    /*
     This not possible to create object of a class that contain pure virtual function
    */
    anotherClass object;
    object.sayHellow();
}

En la clase anotherClass elimine la función sayHellow y ejecute el código. ¡obtendrá un error! Porque cuando una clase contiene una función virtual pura, no se puede crear ningún objeto a partir de esa clase y se hereda, entonces su clase derivada debe implementar esa función.

Función virtual

prueba con otro código

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()
    {
        cout<<"from base\n";
    }

};

class anotherClass:public aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"from derived \n";
    }

};
int main()
{
    aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
    baseObject->sayHellow();///call base one

    baseObject=new anotherClass;
    baseObject->sayHellow();////call the derived one!

}

Aquí la función sayHellow está marcada como virtual en la clase base. Dice el compilador que intenta buscar la función en la clase derivada e implementa la función. Si no se encuentra, ejecute la base. Gracias

Tunvir Rahman Tusher
fuente
Jaja, me tomó un largo de 30 segundos para entender lo que está mal aquí ... HELLOW :)
Hans
0

"Una función virtual o método virtual es una función o método cuyo comportamiento puede ser anulado dentro de una clase heredada por una función con la misma firma" - wikipedia

Esta no es una buena explicación para las funciones virtuales. Porque, incluso si un miembro no es virtual, las clases heredadas pueden anularlo. Puedes intentar verlo tú mismo.

La diferencia se muestra cuando una función toma una clase base como parámetro. Cuando da una clase heredada como entrada, esa función usa la implementación de la clase base de la función anulada. Sin embargo, si esa función es virtual, utiliza la que se implementa en la clase derivada.

lata
fuente
0
  • Las funciones virtuales deben tener una definición en la clase base y también en la clase derivada, pero no es necesaria, por ejemplo, la función ToString () o toString () es una Virtual, por lo que puede proporcionar su propia implementación al anularla en las clases definidas por el usuario.

  • Las funciones virtuales se declaran y definen en la clase normal.

  • La función virtual pura debe declararse terminando con "= 0" y solo puede declararse en clase abstracta.

  • Una clase abstracta que tiene una (s) función (es) virtual (s) pura (s) no puede tener una (s) definición (es) de esa función virtual pura, por lo que implica que la implementación debe proporcionarse en la (s) clase (s) que derivan de esa clase abstracta.

Sohail xIN3N
fuente
La misma nota que para @rashedcs: de hecho, una función virtual pura puede tener su definición ...
Jarek C