poo modo herencia derivadas clases c++ oop polymorphism virtual-functions base-class

c++ - derivadas - la herencia es un modo de



¿Por qué usar punteros de clase base para clases derivadas? (4)

El poder del polimorfismo no es realmente evidente en su ejemplo simple, pero si lo amplía un poco podría volverse más claro.

class vehicle{ ..... virtual int getEmission(); } class car : public vehicle{ int getEmission(); } class bus : public vehicle{ int getEmission(); } int main() { car a; car b; car c; bus d; bus e; vehicle *traffic[]={&a,&b,&c,&d,&e}; int totalEmission=0; for(int i=0;i<5;i++) { totalEmission+=traffic[i]->getEmission(); } }

Esto le permite recorrer una lista de punteros y hacer que se llamen diferentes métodos dependiendo del tipo subyacente. Básicamente, le permite escribir código donde no necesita saber cuál es el tipo de niño en el momento de la compilación, pero el código realizará la función correcta de todos modos.

class base{ ..... virtual void function1(); virtual void function2(); }; class derived::public base{ int function1(); int function2(); }; int main() { derived d; base *b = &d; int k = b->function1() // Why use this instead of the following line? int k = d.function1(); // With this, the need for virtual functions is gone, right? }

No soy un ingeniero de CompSci y me gustaría saber esto. ¿Por qué usar funciones virtuales si podemos evitar los punteros de clase base?


Es para implementar el polimorfismo. A menos que tenga un puntero de clase base que apunte a un objeto derivado, no puede tener polimorfismo aquí.

Una de las características clave de las clases derivadas es que un puntero a una clase derivada es compatible con el tipo con un puntero a su clase base. El polimorfismo es el arte de aprovechar esta característica simple pero potente y versátil, que brinda a las metodologías orientadas a objetos todo su potencial.

En C ++, existe una relación especial de tipo / subtipo en la que un puntero de clase base o una referencia puede abordar cualquiera de sus subtipos de clase derivados sin la intervención del programador. Esta capacidad de manipular más de un tipo con un puntero o una referencia a una clase base se conoce como polimorfismo.

El polimorfismo de subtipo nos permite escribir el núcleo de nuestra aplicación independientemente de los tipos individuales que deseamos manipular. Más bien, programamos la interfaz pública de la clase base de nuestra abstracción a través de punteros y referencias de clase base. En tiempo de ejecución, el tipo real al que se hace referencia se resuelve y se invoca la instancia apropiada de la interfaz pública. La resolución en tiempo de ejecución de la función apropiada para invocar se denomina vinculación dinámica (de forma predeterminada, las funciones se resuelven estáticamente en tiempo de compilación). En C ++, el enlace dinámico se admite mediante un mecanismo denominado funciones virtuales de clase. El polimorfismo de subtipo a través de la herencia y el enlace dinámico proporcionan la base para la programación orientada a objetos

El beneficio principal de una jerarquía de herencia es que podemos programar a la interfaz pública de la clase base abstracta en lugar de a los tipos individuales que forman su jerarquía de herencia, protegiendo así nuestro código de los cambios en esa jerarquía. Definimos eval (), por ejemplo, como una función virtual pública de la clase base de consulta abstracta. Al escribir código como _rop->eval(); El código de usuario está protegido de la variedad y la volatilidad de nuestro lenguaje de consulta. Esto no solo permite la adición, revisión o eliminación de tipos sin requerir cambios en los programas del usuario, sino que también libera al proveedor de un nuevo tipo de consulta de tener que recodificar el comportamiento o las acciones comunes a todos los tipos en la propia jerarquía. Esto se apoya en dos características especiales de la herencia: polimorfismo y unión dinámica. Cuando hablamos de polimorfismo dentro de C ++, nos referimos principalmente a la capacidad de un puntero o una referencia de una clase base para abordar cualquiera de sus clases derivadas. Por ejemplo, si definimos una función no miembro (eval) como sigue, // pquery puede abordar cualquiera de las clases derivadas de la consulta void eval( const Query *pquery ) { pquery->eval(); } void eval( const Query *pquery ) { pquery->eval(); } podemos invocarlo legalmente, pasando la dirección de un objeto de cualquiera de los cuatro tipos de consulta:

int main() { AndQuery aq; NotQuery notq; OrQuery *oq = new OrQuery; NameQuery nq( "Botticelli" ); // ok: each is derived from Query // compiler converts to base class automatically eval( &aq ); eval( &notq ); eval( oq ); eval( &nq ); }

mientras que un intento de invocar eval () con la dirección de un objeto no derivado de Query resulta en un error en tiempo de compilación:

int main() { string name("Scooby-Doo" ); // error: string is not derived from Query eval( &name); }

Dentro de eval (), la ejecución de pquery-> eval (); debe invocar la función miembro virtual eval () apropiada en función de las direcciones de consulta de objetos de clase reales. En el ejemplo anterior, pquery a su vez aborda un objeto AndQuery, un objeto NotQuery, un objeto OrQuery y un objeto NameQuery. En cada punto de invocación durante la ejecución de nuestro programa, se determina el tipo de clase real abordado por pquery y se llama la instancia eval () apropiada. La unión dinámica es el mecanismo a través del cual se logra esto. En el paradigma orientado a objetos, el programador manipula una instancia desconocida de un conjunto de tipos limitado pero infinito. (El conjunto de tipos está limitado por su jerarquía de herencia. En teoría, sin embargo, no hay límite para la profundidad y amplitud de esa jerarquía.) En C ++ esto se logra mediante la manipulación de objetos a través de punteros y referencias de clase base solamente. En el paradigma basado en objetos, el programador manipula una instancia de un tipo fijo y singular que está completamente definido en el punto de compilación. Aunque la manipulación polimórfica de un objeto requiere que se acceda al objeto a través de un puntero o una referencia, la manipulación de un puntero o una referencia en C ++ no necesariamente resulta en un polimorfismo. Por ejemplo, considere

// no polymorphism int *pi; // no language-supported polymorphism void *pvi; // ok: pquery may address any Query derivation Query *pquery;

En C ++, el polimorfismo existe solo dentro de jerarquías de clases individuales. Los punteros de tipo void * se pueden describir como polimórficos, pero no tienen un soporte explícito de lenguaje, es decir, deben ser manejados por el programador a través de conversiones explícitas y alguna forma de discriminación que realiza un seguimiento del tipo real que se está tratando.


Parece que has hecho dos preguntas (en el título y al final):

  1. ¿Por qué usar punteros de clase base para clases derivadas? Este es el uso mismo del polimorfismo. Le permite tratar los objetos de manera uniforme mientras le permite tener una implementación específica. Si esto te molesta, supongo que deberías preguntar: ¿Por qué el polimorfismo?

  2. ¿Por qué usar destructores virtuales si podemos evitar los punteros de clase base? El problema aquí es que no siempre se pueden evitar los punteros de clase base para explotar la fuerza del polimorfismo.


Usted tiene razón, si tiene un objeto que no necesita referirse a él a través de un puntero. Tampoco necesita un destructor virtual cuando el objeto se destruirá según el tipo de creación.

La utilidad viene cuando se obtiene un puntero a un objeto desde otra pieza de código, y realmente no se sabe cuál es el tipo más derivado. Puede tener dos o más tipos derivados construidos en la misma base y una función que devuelve un puntero al tipo base. Las funciones virtuales le permitirán usar el puntero sin preocuparse por qué tipo derivado está usando, hasta que sea el momento de destruir el objeto. El destructor virtual destruirá el objeto sin que usted sepa a qué clase derivada corresponde.

Aquí está el ejemplo más simple de usar funciones virtuales:

base *b = new derived; b->function1(); delete b;