2011-02-03 16 views
8

Sé que las clases derivadas pueden simplemente "redefinir" funciones de miembro de clase base y que cuando se llama a esa función de un objeto de clase derivado , la función definida en se utiliza la clase derivada, pero ... ¿Esto no hace que la palabra clave "virtual" sea redundante? He leído de algunas diferencias obviamente significativas entre estos dos casos (es decir: si tiene un puntero de clase base apuntando a una clase derivada y llama a una función, si es virtual la función de clase derivada será llamada, pero si no, se llamará a la función de clase base).C++ pregunta sobre polimorfismo/herencia: redefinición de funciones base frente a funciones virtuales

Dicho de otra manera, ¿cuál es el propósito de ser capaz de redefinir las funciones miembro como funciones no virtuales, y esto es un uso común práctica?

Personalmente, me parece que sería muy confuso.

Gracias!

+2

Suena como si necesitaras [un buen libro de C++] (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) –

Respuesta

1

Funciona para una instancia de la clase derivada y un puntero a la clase derivada. Sin embargo, si pasa su clase derivada a una función que toma un puntero a Base, se llamará a la versión Base de la función. Esto probablemente no es deseable. Por ejemplo, la siguiente volverá 5

#include "stdafx.h" 
#include <conio.h> 
#include <iostream> 

class Base 
{ 
public: 
    int Foo(){return 5;} 
}; 

class Derived:public Base 
{ 
    int Foo(){return 6;} 
}; 

int Func(Base* base) 
{ 
    return base->Foo(); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Derived asdf; 

    std::cout << Func(&asdf); 
    getch(); 

    return 0; 
} 

Esto es debido a los trabajos virtuales manera. Cuando un objeto tiene una llamada virtual, la función correcta se busca en la tabla v cuando se llama a la función virtual. De lo contrario, no tiene realmente herencia, tiene el puntero Base actuando como la clase base, no como la clase derivada.

4

El enfoque más común en los lenguajes de OOP más comunes (Java, SmallTalk, Python, etc.) es tener, de forma predeterminada, cada miembro como función virtual.

El inconveniente es que hay una pequeña penalización de rendimiento por cada vez que se realiza una llamada virtual. Por ese motivo, C++ le permite elegir si desea que sus métodos se definan como virtuales o no.

Sin embargo, hay una diferencia muy importante entre un método virtual y uno no virtual. Por ejemplo:

class SomeClass { ... }; 
class SomeSubclassOfSomeClass : public SomeClass { ... }; 
class AnotherSubclassOfSomeClass : public SomeClass { ... }; 

SomeClass* p = ...; 

p->someVirtualMethod(); 

p->someNonVirtualMethod(); 

El código real ejecutada cuando la llamada se hace someVirtualMethod depende del tipo concreto de referencia el puntero p, dependiendo enteramente de SomeClass subclases redefinición.

Pero el código ejecutado en la llamada someNonVirtualMethod es claro: siempre el de SomeClass, ya que el tipo de la variable p es SomeClass.

0

Es importante tener cuidado cuando los destructores están marcados como no virtuales y "reemplazados" en las clases derivadas: es posible que la clase no se limpie correctamente si el destructor se invoca en un puntero a la clase base.

0

Parece que no ha descubierto el poder del polimorfismo.El polimorfismo funciona de esta manera:

Tiene una función que toma un puntero de una clase base, puede llamarla pasando objetos de clase derivados, de acuerdo con la implementación diferente de la clase derivada, la función actuará de manera diferente. Es una manera maravillosa porque la función es estable, incluso si ampliamos la jerarquía del árbol de herencia.

Sin este mecanismo, no puede lograr esto. Y este mecanismo necesita "virtual"

1

Direccionamiento: "Dicho de otra manera, ¿cuál es el propósito de poder redefinir las funciones de los miembros como funciones no virtuales, y esta es una práctica común?"

Bueno, no se puede. Si el método de la clase base es virtual, también lo es el método de clase derivada correspondiente, si existe, independientemente de si se usa la palabra clave 'virtual'.

Entonces: "¿Esto no hace que la palabra clave" virtual "sea redundante?". Sí, es es redundante en el método de clase derivada, pero no en la clase base.

Sin embargo, tenga en cuenta que es inusual (siendo cortés) desear tener un método no virtual y luego redefinirlo una clase derivada.

2

Parece que ya sabes la diferencia entre los métodos virtuales y no virtuales, así que no entraré en eso como otros lo han hecho. La pregunta es, ¿cuándo sería más útil un método no virtual que uno virtual?

hay casos en los que no desea la sobrecarga de tener un puntero vtable incluido en cada objeto, por lo que se toma la molestia de asegurarse de que no hay métodos virtuales en la clase. Tomemos por ejemplo una clase que represente un punto y tenga dos miembros, x y y - puede tener una gran colección de estos puntos, y un puntero vtable aumentaría el tamaño del objeto al menos en un 50%.

0

Las reglas de búsqueda de nombres y C++ permiten cosas muy extrañas, y los métodos no están solos aquí. De hecho Ocultación (o remedo) puede ocurrir en muchas situaciones diferentes:

int i = 3; 
for (int i = 0; i != 5; ++i) { ... } // the `i` in `for` hides the `i` out of it 

struct Base 
{ 
    void foo(); 
    int member; 
}; 

struct Derived: Base 
{ 
    void foo(); // hides Base::foo 
    int member; // hides Base::member 
}; 

¿Por qué entonces? Para la resiliencia.

Al modificar una clase Base no conozco todos sus posibles hijos. Debido a la regla de ocultación (ya pesar de la confusión que puede crear), puedo añadir un atributo o método y utilizarlo sin un cuidado en el mundo:

  • Mi llamada será siempre llamar al método Base, si algún niño anularlo o no
  • una llamada en la que el niño no se verá afectada, por lo tanto no voy repente chocar código de algún otro programador

es evidente que si nos fijamos en el programa como una obra finita que no tiene sentido, pero los programas están evolucionando y la regla de ocultación facilita la evolución.

0

La forma más sencilla de explicarlo es probablemente la siguiente:

virtual hace algunas operaciones de búsqueda para que, mediante la adición de una tabla de consulta virtual.

En otras palabras, si usted no tiene la palabra clave virtual, y sustituimos un método, todavía tendría que llamar a ese método manualmente [perdóname si mi memoria para sintaxis de C++ es un poco oxidado en puntos] :

class A { void doSomething() { cout << "1"; } } 
class B: public A { void doSomething() { cout << "2"; } } 
class C: public A { void doSomething() { cout << "3"; } } 

void someOtherFunc(A* thing) { 
    if (typeid(thing) == typeid(B)) { 
     static_cast<B*>(thing)->doSomething(); 
    } else if (typeid(thing) == typeid(C)) { 
     static_cast<C*>(thing)->doSomething(); 
    } else { 
     // not a derived class -- just call A's method 
     thing->doSomething(); 
    } 
} 

Usted puede optimizar esta un poco (para facilitar la lectura y el rendimiento, lo más probable), usando una tabla de consulta:

typedef doSomethingWithAnA(A::*doSomethingPtr)(); 
map<type_info, doSomethingWithAnA> A_doSomethingVTable; 

void someOtherFuncA* thing) { 
    doSomethingWithAnA methodToCall = A_doSomethingVTable[typeid(thing)]; 
    thing->(*methodToCall)(); 
} 

Ahora, que es más de un enfoque de alto nivel. El compilador C++ obviamente puede optimizar esto un poco más, al saber exactamente qué es "type_info", y así sucesivamente. Probablemente, en lugar de ese "mapa" y la búsqueda "methodToCall = aDoSomethingVTable [typeid (thing)]", then call, ", el compilador está insertando algo mucho más pequeño y rápido, como" doSomethingWithAnA * A_doSomethingVTable; "seguido de" A_doSomethingTablething -> type_number"

Así que tienes razón en que C++ no necesita realmente virtual, pero se le añade una gran cantidad de azúcar sintáctico para hacer su vida más fácil, y puede optimizar mejor también

que.. dicho, todavía creo que C++ es un lenguaje horriblemente obsoleto, con muchas complicaciones innecesarias. Virtual, por ejemplo, podría (y probablemente debería) ser asumido por defecto, y optimizado fuera de lugar innecesario. Una palabra clave "anular" como Scala sería mucho más uso más que "virtual".

Cuestiones relacionadas