c++ - que - ¿Por qué usar funciones virtuales?
que es un objeto en c++ (4)
Piense en la clase de animales, y derivados de ella son gato, perro y vaca. La clase de animal tiene un
virtual void SaySomething()
{
cout << "Something";
}
función.
Animal *a;
a = new Dog();
a->SaySomething();
En lugar de imprimir "Something", el perro debe decir "Bark", el gato debe decir "Meow". En este ejemplo, ves que a es un perro, pero algunas veces tienes un puntero de animal y no sabes de qué animal se trata. No quiere saber qué animal es, solo quiere que el animal diga algo. Así que solo llama a la función virtual y los gatos dicen "miau" y los perros dicen "ladran".
Por supuesto, la función SaySomething debería haber sido puramente virtual para evitar posibles errores.
Posible duplicado:
¿Alguien puede explicar los métodos virtuales de C ++?
Tengo una pregunta sobre las funciones virtuales de C ++.
¿Por qué y cuándo usamos funciones virtuales? ¿Alguien puede darme una implementación en tiempo real o el uso de funciones virtuales?
Utiliza funciones virtuales cuando desea anular un determinado comportamiento (método de lectura) para su clase derivada en lugar de la implementada para la clase base y desea hacerlo en tiempo de ejecución mediante un puntero a la clase base.
El ejemplo clásico es cuando tienes una clase base llamada Shape
y formas concretas (clases) que se derivan de ella. Cada clase concreta sobrescribe (implementa un método virtual) llamada Draw()
.
La jerarquía de clases es la siguiente:
El siguiente fragmento muestra el uso del ejemplo; crea una matriz de punteros de clase Shape
donde cada uno apunta a un objeto de clase derivado distinto. En tiempo de ejecución, invocar el método Draw()
da como resultado la invocación del método anulado por esa clase derivada y la Shape
particular se dibuja (o representa).
Shape *basep[] = { &line_obj, &tri_obj,
&rect_obj, &cir_obj};
for (i = 0; i < NO_PICTURES; i++)
basep[i] -> Draw ();
El programa anterior simplemente usa el puntero a la clase base para almacenar las direcciones de los objetos de clase derivados. Esto proporciona un acoplamiento flexible porque el programa no tiene que cambiar drásticamente si se agrega una nueva clase de shape
derivada de concreto en cualquier momento. La razón es que hay segmentos de código mínimos que realmente usan (dependen) en el tipo concreto de Shape
.
Lo anterior es un buen ejemplo del Principio Abierto Cerrado de los famosos principios de diseño SOLID .
Utiliza funciones virtuales cuando necesita manejar diferentes objetos de la misma manera. Se llama polimorfismo. Imaginemos que tiene alguna clase base, algo así como la forma clásica:
class Shape
{
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
class Rectange: public Shape
{
public:
void draw() { // draw rectangle here }
};
class Circle: public Shape
{
public:
void draw() { // draw circle here }
};
Ahora puedes tener vectores de diferentes formas:
vector<Shape*> shapes;
shapes.push_back(new Rectangle());
shapes.push_back(new Circle());
Y puedes dibujar todas las formas de esta manera:
for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
{
(*i)->draw();
}
De esta forma estás dibujando diferentes formas con un método virtual - draw (). La versión correcta del método se selecciona según la información del tiempo de ejecución sobre el tipo de objeto detrás del puntero.
Aviso Cuando utiliza funciones virtuales puede declararlas como virtuales puras (como en la clase Shape, simplemente coloque "= 0" después del método proto). En este caso, no podrá crear una instancia de objeto con función virtual pura y se llamará clase abstracta.
También note "virtual" antes del destructor. En el caso de que esté planeando trabajar con objetos mediante punteros a sus clases base, debe declarar el destructor virtual, de modo que cuando llame a "eliminar" para el puntero de clase base, se invocará toda la cadena de destructores y no habrá pérdidas de memoria.
Utilizaría una función virtual para implementar el "polimorfismo", en particular donde tiene un objeto, no sabe cuál es el tipo real subyacente, pero sepa qué operación desea realizar en él, y la implementación de esto (cómo lo hace) difiere según el tipo que realmente tenga.
Esencialmente lo que comúnmente se llama el "Principio de sustitución de Liskov", que lleva el nombre de Barbara Liskov, quien habló sobre esto alrededor de 1983.
Donde necesita utilizar decisiones dinámicas de tiempo de ejecución donde, en el momento en que se llama al código que invoca la función, no sabe qué tipos pueden pasar por él, ya sea ahora o en el futuro, este es un buen modelo para usar.
No es la única manera sin embargo. Hay todo tipo de "devoluciones de llamada" que pueden tomar una "cantidad de datos" y es posible que tenga tablas de devoluciones de llamadas dependientes de un bloque de encabezado en los datos que entran, por ejemplo, un procesador de mensajes. Para esto no es necesario usar una función virtual, de hecho, lo que probablemente usaría es una especie de cómo una tabla v se implementa solo con una entrada (por ejemplo, una clase con una sola función virtual).