2010-05-28 17 views
36

¿Cuál es el propósito de utilizar la palabra reservada virtual en frente de las funciones? Si quiero que una clase secundaria anule una función principal, solo declaro la misma función, como void draw(){}.Anulación vs virtual

class Parent { 
public: 
    void say() { 
     std::cout << "1"; 
    } 
}; 

class Child : public Parent { 
public: 
    void say() 
    { 
     std::cout << "2"; 
    } 
}; 

int main() 
{ 
    Child* a = new Child(); 
    a->say(); 
    return 0; 
} 

La salida es 2.

Así que de nuevo, ¿por qué virtual ser necesaria la palabra reservada en la cabecera de say()?

Gracias un montón.

Respuesta

17

Ésta es la clásica pregunta de cómo funciona el polimorfismo creo. La idea principal es que desee abstraer el tipo específico para cada objeto. En otras palabras: ¡quiere poder llamar a las instancias de Child sin saber que es un niño!

Aquí hay un ejemplo: Suponiendo que tiene la clase "Child" y la clase "Child2" y "Child3", desea poder referirse a ellos a través de su clase base (Parent).

Parent[3] parents; 
parents[0] = new Child(); 
parents[1] = new Child2(); 
parents[2] = new Child3(); 

for (int i=0;i<3;++i) 
parents[i]->say(); 

Como se puede imaginar, esto es muy poderoso. Te permite extender el elemento Parent tantas veces como quieras y las funciones que toman un puntero de Padre funcionarán. Para que esto funcione, como mencionan otros, debe declarar el método como virtual.

+0

Creo que un ejemplo explícito hubiera sido muy apreciado. – jokoon

2

Cuando utiliza la palabra clave virtual, se crea una tabla de funciones virtuales para ubicar los métodos correctos en una instancia. Entonces, incluso si la instancia derivada apunta a un puntero de clase base, aún encontrará la implementación correcta del método.

37

Si la función eran virtual, entonces se podría hacer esto y aún así obtener la salida "2":

Parent* a = new Child(); 
a->say(); 

Esto funciona porque una función virtual utiliza el real tipo mientras que un uso de funciones no virtuales el tipo declarado. Lea en polymorphism para una mejor discusión de por qué desea hacer esto.

+0

Esto se hace todo el tiempo, el ejemplo clásico sería donde 'Parent' es, por ejemplo,' Shape', y el niño es un tipo específico de forma (como un 'Square'). Luego reemplace 'say' con, por ejemplo,' draw'. ¿Ves por qué eso sería útil? Es exactamente el mismo ejemplo que en la pregunta de OP, solo que con diferentes palabras. – Donnie

+0

¡Buen ejemplo! ... Pero ¿por qué haces esto todo el tiempo? ¿Por qué no Square * sq = new Square(); ¿en primer lugar? – user1511417

+3

No lo hace todo el tiempo, lo hace cuando es apropiado. ¿Qué sucede si está creando una aplicación de dibujo y permite que la gente escoja pinceles de forma? Necesita una variable global (o al menos el nivel de objeto), pero no sabe qué tipo de forma elegirán con antelación. – Donnie

24

Prueba con:

Parent *a = new Child(); 
Parent *b = new Parent(); 

a->say(); 
b->say(); 

Sin virtual, tanto con la impresión '1'. Agregue virtual, y el elemento secundario actuará como un elemento secundario, aunque se lo consulte mediante un puntero a Parent.

+0

entonces, excepto cuando lanzas un objeto o cuando usas un constructor derivado, ¿no hay forma de diferenciar entre un método normal anulado y un método virtual sobrecargado? – jokoon

+0

Creo que este es el mejor ejemplo para usar al explicar la diferencia entre sobrescribir y virtual. – rkioji

0

Este es un aspecto muy importante de la programación en C++: casi cada entrevista en la que he estado, me hacen esta pregunta.

¿Qué ocurre si cambia su principal a:

int main() { Parent* a = new Child(); a->say(); return 0; } 

Además, vale la pena entender lo que es un vtable es.

14

Si no utiliza la palabra clave virtual no está sobrescribiendo, pero rahter está definiendo un método no relacionado en la clase derivada que ocultará el método de la clase base. Es decir, sin virtual, Base::say y Derived::say no están relacionados, además del nombre de coincidencia.

Cuando se utiliza la palabra clave virtual (requerido en la base, opcional en la clase derivada), le está diciendo al compilador que las clases que se derivan de esta base podrán anulación del método. En ese caso, Base::say y Derived::say se consideran anulaciones del mismo método.

Cuando se utiliza una referencia o puntero a una clase base para llamar a un método virtual, el compilador añadirá el código correspondiente para que el última overrider se llama (la anulación de la clase más derivada que define el método de la jerarquía de la instancia concreta en uso). Tenga en cuenta que si no utiliza referencias/puntero sino variables locales, el compilador puede resolver la llamada y no necesita usar el mecanismo de despacho virtual.

11

Bueno he comprobado por mí mismo, porque hay una gran cantidad de cosas que podemos pensar en:

#include <iostream> 
using namespace std; 
class A 
{ 
public: 
    virtual void v() { cout << "A virtual" << endl; } 
    void f() { cout << "A plain" << endl; } 
}; 

class B : public A 
{ 
public: 
    virtual void v() { cout << "B virtual" << endl; } 
    void f() { cout << "B plain" << endl; } 
}; 

class C : public B 
{ 
public: 
    virtual void v() { cout << "C virtual" << endl; } 
    void f() { cout << "C plain" << endl; } 
}; 

int main() 
{ 
    A * a = new C; 
    a->f(); 
    a->v(); 

    ((B*)a)->f(); 
    ((B*)a)->v(); 
} 

de salida:

A plain 
C virtual 
B plain 
C virtual 

Creo que una respuesta buena, simple y corto podría parecerse a esto (porque creo que las personas que pueden entender más puede memorizar por lo tanto menos necesidad de explicación breve y sencilla):

métodos cheques virtuales para los datos de la instancia del puntero señala, mientras que los métodos clásicos no llaman al método correspondiente al tipo especificado.

El punto de esa característica es la siguiente: supongamos que tiene una serie de Atléticos. La matriz puede contener B, C, (o incluso tipos derivados). si desea llamar secuencialmente el mismo método de todas esas instancias, debería llamar a cada una de las que haya sobrecargado.

Me resulta bastante difícil de entender, y obviamente cualquier curso de C++ debería explicar cómo se logra esto, porque la mayoría de las veces solo se les enseña sobre funciones virtuales, pero hasta que comprendan cómo el compilador las entiende y cómo el ejecutable manejará las llamadas, estás en la oscuridad.

Lo que pasa con VFtables es que nunca me han explicado qué tipo de código agrega, y obviamente es aquí donde C++ requiere mucha más experiencia que C, y esta podría ser la razón principal por la que C++ fue etiquetado como "lento" en sus primeros días: de hecho, es poderoso, pero al igual que todo, es poderoso si sabes cómo usarlo, o simplemente te "volarás toda la pierna".

+1

buen ejemplo de código +1 – fusi