2009-12-01 13 views
5

Tengo problemas para entender el propósito de la palabra clave virtual en C++. Sé C y Java muy bien, pero soy nuevo en C++Problemas para comprender C++ `virtual`

de Wikipedia

En la programación orientada a objetos, una función virtual o método virtual es una función o un método cuyo comportamiento puede se anulará dentro de una clase heredada por una función con la misma firma .

Sin embargo, puedo reemplazar un método como se verá más adelante sin utilizar la palabra clave virtual

#include <iostream> 

using namespace std; 

class A { 
    public: 
     int a(); 
}; 

int A::a() { 
    return 1; 
} 

class B : A { 
    public: 
     int a(); 
}; 

int B::a() { 
    return 2; 
} 

int main() { 
    B b; 
    cout << b.a() << endl; 
    return 0; 
} 

//output: 2 

Como se puede ver a continuación, la función A :: a se anulados con éxito con B :: sin requerir un virtual

Para agravar mi confusión es esta declaración acerca de destructores virtuales, también de Wikipedia

como se ilustra en el siguiente ejemplo, , es importante que una clase base de C++ tenga un destructor virtual para asegurar que se llamará siempre al destructor de la clase derivada más.

¿Así que virtual también le dice al compilador que llame a los destructores de los padres? Esto parece ser muy diferente de mi entendimiento original de virtual como "hacer que la función reemplazable"

Respuesta

16

Realice los siguientes cambios y verá por qué:

#include <iostream> 

using namespace std; 

class A { 
    public: 
     int a(); 
}; 

int A::a() { 
    return 1; 
} 

class B : public A { // Notice public added here 
    public: 
     int a(); 
}; 

int B::a() { 
    return 2; 
} 

int main() { 
    A* b = new B(); // Notice we are using a base class pointer here 
    cout << b->a() << endl; // This will print 1 instead of 2 
    delete b; // Added delete to free b 
    return 0; 
} 

Ahora, para hacer que funcione como usted pretende:

#include <iostream> 

using namespace std; 

class A { 
    public: 
     virtual int a(); // Notice virtual added here 
}; 

int A::a() { 
    return 1; 
} 

class B : public A { // Notice public added here 
    public: 
     virtual int a(); // Notice virtual added here, but not necessary in C++ 
}; 

int B::a() { 
    return 2; 
} 

int main() { 
    A* b = new B(); // Notice we are using a base class pointer here 
    cout << b->a() << endl; // This will print 2 as intended 
    delete b; // Added delete to free b 
    return 0; 
} 

La nota que ha incluido sobre los destructores virtuales es exactamente correcta. En su muestra no hay nada que deba ser limpiado, pero digamos que tanto A como B tenían destructores. Si no están marcados como virtuales, ¿cuál de ellos se llamará con el puntero de la clase base? Sugerencia: Funcionará exactamente igual que el método a() cuando no se marcó como virtual.

+2

No hay necesidad de complicar el ejemplo con new/delete: 'B obj; A * p = &obj; 'o' A & ref = obj; ' –

+1

Es cierto: de hecho omití la eliminación en mis primeras ediciones; pero la semántica de desreferenciación en C++ también es complicada ... ;-) – ConsultUtah

0

Pruebe ((A *) & b) .a() y vea lo que se llama entonces.

La palabra reservada virtual le permite tratar un objeto de una manera abstracta (es decir, mediante un puntero de clase base) y aún así llamar a código descendiente ...

Dicho de otra manera, la palabra clave virtual "permite antigua llamada codificada nueva código". Puede haber escrito un código para operar en A, pero a través de funciones virtuales, ese código puede llamar al más nuevo a() de B.

9

Se podría pensar de la siguiente manera.

Todas las funciones en Java son virtuales. Si tiene una clase con una función y anula esa función en una clase derivada, se llamará, sin importar el tipo declarado de la variable que usa para llamarla.

En C++, por otro lado, no será necesariamente llamado.

Si usted tiene una base de clase base y una clase derivada derivada, y ambos tienen una función no virtual en ellos, llamado 'foo', entonces

Base * base; 
Derived *derived; 

base->foo(); // calls Base::foo 
derived->foo(); // calls Derived::foo 

Si foo es virtual, entonces tanto la llamada Derivado :: foo.

+0

Escrito con muy buen gusto; conciso. –

1

Como se puede ver a continuación, la función A :: se anula un éxito con B :: sin requerir una virtuales

Se puede, o puede no funcionar. En su ejemplo, funciona, pero es porque usted crea y usa un objeto B directamente, y no a través del puntero al A. Ver C++ FAQ Lite, 20.3.

¿Así que virtual también le dice al compilador que llame a los destructores de los padres?

Se necesita un destructor virtual si elimina un puntero de la clase base que apunta a un objeto de clase derivada, y espera que se ejecuten los destructores tanto base como derivados. Ver C++ FAQ Lite, 20.7.

2

virtual significa que el método real se determina en tiempo de ejecución en función de qué clase se creó una instancia y no del tipo que se utilizó para declarar la variable. En su caso esto es una anulación estática que irá por el método definido para la clase B no importa lo que era el tipo real del objeto creado

0

Digamos que ejemplarizado B pero se mantuvo como una instancia de un A:

A *a = new B(); 

y llamado función a() cuya implementación de a() se llamará?

Si se llama a() no es virtual A se llamará. Si a() fuera virtual, se invocaría la versión de subclase instanciada de a() independientemente de cómo la tenga.

Si constructor asignado toneladas de B de memoria para matrices o archivos abiertos, llamando

delete a; 

aseguraría destructor de B fue llamado independientemente de cómo se está celebrando, ya sea por una clase base o interfaz o lo que sea .

Buena pregunta por cierto.

+3

Creo que tu intención era 'a' ser un puntero. –

+0

Vaya. Gracias. Adivina quién ha estado usando nada más que VB.net y C# durante tres años. –

1

Necesita el virtual si usa un puntero de clase base como consultutah (y otros mientras estoy escribiendo;)) lo dice.

La falta de virtuales permite guardar una comprobación para saber qué método necesita para llamar (el de la clase base o de algunos derivados). Sin embargo, en este punto no te preocupes por el rendimiento, solo por el comportamiento correcto.

El destructor virtual es particularmente importante porque las clases derivadas pueden declarar otras variables en el montón (es decir, utilizando la palabra clave 'nuevo') y debe poder eliminarlo.

Sin embargo,, puede notar que en C++ tiende a usar menos derivaciones que en java, por ejemplo (a menudo usa plantillas para un uso similar), y tal vez ni siquiera necesita preocuparse por eso . Además, si nunca declaras tus objetos en el montón ("A a;" en lugar de "A * a = new A();") entonces tampoco debes preocuparte por ello. Por supuesto, esto dependerá en gran medida de qué/cómo se desarrolle y si planifica que alguien más obtendrá su clase o no.

+1

No se trata de dónde se asignan los objetos, se trata de utilizarlos a través de punteros y referencias a clases base, incluido el "esto" implícito en los métodos. –

2

¿Así que virtual también le dice al compilador que llame a los destructores de los padres? Esto parece ser muy diferente de mi comprensión original de virtual como "hacer que la función sea reemplazable"

Su original y su nuevo entendimiento son incorrectos.

  • Métodos (que ellos llaman funciones) son siempre reemplazable. No importa si es virtual, puro, no virtual o algo así.
  • Destructores principales son siempre llamado. Como son los constructores.

"Virtual" solo hace la diferencia si llama a un método mediante un puntero de tipo pointer-to-baseclass. Dado que en su ejemplo no utiliza punteros en absoluto, virtual no hace una diferencia en absoluto.

Si utiliza una variable a de tipo puntero-a-A, es decir A* a;, no solo puede asignar otras variables de tipo puntero-a-A, sino también variables de tipo puntero-a-B, porque B se deriva de A.

A* a; 
B* b; 

b = new B(); // create a object of type B. 
a = b;  // this is valid code. a has still the type pointer-to-A, 
      // but the value it holds is b, a pointer to a B object. 

a.a();  // now here is the difference. If a() is non-virtual, A::a() 
      // will be called, because a is of type pointer-to-A. 
      // Whether the object it points to is of type A, B or 
      // something entirely different doesn't matter, what gets called 
      // is determined during compile time from the type of a. 

a.a();  // now if a() is virtual, B::a() will be called, the compiler 
      // looks during runtime at the value of a, sees that it points 
      // to a B object and uses B::a(). What gets called is determined 
      // from the type of the __value__ of a. 
+0

+1, es posible que desee agregar su declaración de que las funciones son ** siempre ** invalidables. En C++ 0x, la palabra clave "final" debería evitar la anulación. – Tim

0

siempre pienso en ello como si fueran piezas de ajedrez (mi primer experimento con OO).

Un tablero de ajedrez contiene punteros a todas las piezas. Los cuadrados vacíos son punteros NULL. Pero todo lo que sabe es que cada puntero apunta a una pieza de ajedrez. La junta no necesita saber más información. Pero cuando se mueve una pieza, la pizarra no sabe que es un movimiento válido ya que cada ficha tiene una característica diferente sobre cómo se mueve. Entonces la junta necesita verificar con la pieza si la jugada es válida.

Piece* board[8][8]; 

CheckMove(Point const& from,Point const& too) 
{ 
    Piece* piece = board[from.x][from.y]; 
    if (piece != NULL) 
    { 
     if (!piece->checkValidMove(from,too)) 
     { throw std::exception("Bad Move"); 
     } 
     // Other checks. 
    } 
} 

class Piece 
{ 
    virtual bool checkValidMove(Point const& from,Point const& too) = 0; 
}; 

class Queen: public Piece 
{ 
    virtual bool checkValidMove(Point const& from,Point const& too) 
    { 
     if (CheckHorizontalMove(from,too) || CheckVerticalMoce(from,too) || CheckDiagonalMove(from,too)) 
     { 
      ..... 
     } 
    } 
}