C++ Polimorfismo
PolimorfismoLiteralmente significa múltiples formas.
Cuando existe una jerarquía entre clases y las clases se relacionan mediante herencia, se utiliza el polimorfismo.
En C++, el polimorfismo (Polymorphism) es una de las características importantes de la programación orientada a objetos.
El polimorfismo de C++ permite utilizar punteros o referencias de la clase base para llamar a los métodos reescritos de la subclase, de modo que la misma interfaz pueda presentar diferentes comportamientos.
El polimorfismo hace que el código sea más flexible y genérico. El programa puede operar sobre objetos de diferentes tipos a través de punteros o referencias de la clase base, sin necesidad de distinguir explícitamente el tipo de objeto. Esto hace que el código sea más extensible: al añadir una nueva clase de forma, no es necesario modificar el programa principal.
A continuación se presentan varios puntos clave del polimorfismo:
Funciones virtuales (Virtual Functions):
- En la clase base se declara una función como virtual, utilizando la palabra clave
virtual。 - La clase derivada puede reescribir (override) esta función virtual.
- Al llamar a una función virtual, se decide qué versión de la función invocar según el tipo real del objeto.
Enlace dinámico (Dynamic Binding):
- También llamado enlace tardío (Late Binding); determina la implementación específica de la llamada a la función en tiempo de ejecución.
- Se necesita utilizar un puntero o una referencia a la clase base para llamar a la función virtual; el compilador decide en tiempo de ejecución qué función invocar según el tipo real del objeto.
Funciones virtuales puras (Pure Virtual Functions):
- Una clase que contiene funciones virtuales puras se denomina clase abstracta (Abstract Class) y no puede instanciarse directamente.
- Una función virtual pura no tiene cuerpo de función; al declararla se utiliza
= 0。 - Obliga a la clase derivada a proporcionar una implementación concreta.
Mecanismo de implementación del polimorfismo:
- Tabla de funciones virtuales (V-Table): C++ en tiempo de ejecución utiliza la tabla de funciones virtuales para implementar el polimorfismo. Cada clase que contiene funciones virtuales tiene una tabla de funciones virtuales, en la que se almacenan punteros a todas las funciones virtuales de la clase.
- Puntero de función virtual (V-Ptr): el objeto contiene un puntero a la tabla de funciones virtuales de su clase.
Ventajas de utilizar el polimorfismo:
- Reutilización de código: a través de punteros o referencias de la clase base, se pueden operar objetos de diferentes clases derivadas, logrando la reutilización del código.
- Extensibilidad: al añadir una clase derivada nueva, no es necesario modificar el código que depende de la clase base; solo hay que asegurarse de que la nueva clase reescriba correctamente las funciones virtuales.
- Desacoplamiento: el polimorfismo permite que el diseño del programa sea más modular, reduciendo el acoplamiento entre clases.
Consideraciones:
- Solo se produce polimorfismo cuando se llama a una función virtual a través de un puntero o referencia de la clase base.
- Si se utiliza directamente un objeto de la clase derivada para llamar a la función, se invoca la versión de la clase derivada, no la de la clase base.
- El polimorfismo requiere información de tipo en tiempo de ejecución (RTTI), lo que puede aumentar la sobrecarga del programa.
Ejemplo 1
Veamos la aplicación del polimorfismo a través de un ejemplo sencillo:
Ejemplo 1
using namespace std;
// Clase base Animal
class Animal {
public:
// Función virtual sound, proporciona una interfaz para el sonido de diferentes animales
virtual void sound() const {
cout << "Animal makes a sound" << endl;
}
// Destructor virtual asegura que los objetos de las subclases se destruyan correctamente
virtual ~Animal() {
cout << "Animal destroyed" << endl;
}
};
// Clase derivada Dog, hereda de Animal
class Dog : public Animal {
public:
// Reescribe el método sound
void sound() const override {
cout << "Dog barks" << endl;
}
~Dog() {
cout << "Dog destroyed" << endl;
}
};
// Clase derivada Cat, hereda de Animal
class Cat : public Animal {
public:
// Reescribe el método sound
void sound() const override {
cout << "Cat meows" << endl;
}
~Cat() {
cout << "Cat destroyed" << endl;
}
};
// Prueba del polimorfismo
int main() {
Animal* animalPtr; // Puntero de la clase base
// Crea un objeto Dog y lo asigna al puntero Animal
animalPtr = new Dog();
animalPtr->sound(); // Llama al método sound de Dog
delete animalPtr; // Libera memoria, llama a los destructores de Dog y Animal
// Crea un objeto Cat y lo asigna al puntero Animal
animalPtr = new Cat();
animalPtr->sound(); // Llama al método sound de Cat
delete animalPtr; // Libera memoria, llama a los destructores de Cat y Animal
return 0;
}
La salida de la ejecución del programa es:
Dog barks Dog destroyed Animal destroyed Cat meows Cat destroyed Animal destroyed
Explicación del código
Clase baseAnimal:
AnimalLa clase define una función virtualsound(), es una función virtual (virtual), utilizada para representar el comportamiento de sonido de los animales.~Animal()Es un destructor virtual, asegura que al liberar el objeto de la clase derivada apuntado por el puntero de la clase base se llame correctamente al destructor de la clase derivada, evitando fugas de memoria.
Clases derivadasDogyCat:
DogyCatLas clases derivan deAnimalla clase, y cada una implementasound()el método.Dogdesound()Imprime "Dog barks";Catdesound()Imprime "Cat meows". Esto hace que el mismo método (sound()) se comporte de manera diferente en distintas clases.
Función principalmain():
- Crear un puntero de la clase base
animalPtr。 - Usando
new Dog()crearDogun objeto y asignar su dirección aanimalPtr. En este momento, llamar aanimalPtr->sound()producirá "Dog barks", porqueanimalPtrrealmente apunta aDogobjeto. - Al liberar
Dogel objeto, primero se llamaDogal destructor de, y luego se llamaAnimalal destructor de. - Usando
new Cat()crearCatun objeto y asignarlo aanimalPtr, luego llamar aanimalPtr->sound(), producirá "Cat meows", mostrando el comportamiento polimórfico.
Conceptos clave
Función virtual: Al usar en la clase base
virtualla palabra clave para declarar la función virtual, la clase derivada puede reescribir esta función, logrando así que en tiempo de ejecución se llame a la función correcta según el tipo de objeto.Enlace dinámico: El polimorfismo de C++ se implementa mediante enlace dinámico. En tiempo de ejecución, el puntero de la clase base
animalPtrselecciona según el tipo de objeto al que apunta realmente (DogoCat) llamando al correspondientesound()método.Destructor virtual: En una clase base con comportamiento polimórfico, el destructor debe declararse como
virtual, para asegurar que al eliminar un objeto de la clase derivada se llame al destructor de la clase derivada, evitando fugas de recursos.
Ejemplo 2
En el siguiente ejemplo, implementamos mediante polimorfismo una clase base genérica Shape y dos clases derivadas Rectangle y Triangle.
Al llamar a métodos de diferentes clases derivadas a través de un puntero de la clase base, se muestra la característica de enlace dinámico del polimorfismo.
Ejemplo 2
Cuando el código anterior se compila y ejecuta, produce los siguientes resultados:
Rectangle Area: Rectangle class area: 70 Triangle Area: Triangle class area: 25
Análisis del código
Definición de la clase Shape:
Shapees una clase base abstracta que define una función virtualarea()。area()es la función virtual para calcular el área, y se utiliza lavirtualpalabra clave, de modo que en la clase derivada se pueda reescribir esta función y así implementar el polimorfismo.widthyheightSíprotectedatributos, solo se puedenShapeacceder en la clase y en sus clases derivadas.
// 基类 Shape,表示形状
class Shape {
protected:
int width, height; // 宽度和高度
public:
// 构造函数,带有默认参数
Shape(int a = 0, int b = 0) : width(a), height(b) { }
// 虚函数 area,用于计算面积
virtual int area() {
cout << "Shape class area: " << endl;
return 0;
}
};
Definición de la clase Rectangle:
Rectanglehereda deShapela clase, y reescribearea()el método, calculando el área del rectángulo.area()El método utilizaoverridela palabra clave, lo que indica que esto es una reescritura delShapedearea()método de la clase base.Rectangle::area()Devuelvewidth * height, es decir, el área del rectángulo.
// 派生类 Rectangle,表示矩形
class Rectangle : public Shape {
public:
// 构造函数,使用基类构造函数初始化 width 和 height
Rectangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重写 area 函数,计算矩形面积
int area() override {
cout << "Rectangle class area: " << endl;
return width * height;
}
};Definición de la clase Triangle:
TriangleLa clase también hereda deShape, y sobrescribe elarea()método, utilizado para calcular el área del triángulo.Triangle::area()Devuelvewidth * height / 2, que es la fórmula del área del triángulo.
// 派生类 Triangle,表示三角形
class Triangle : public Shape {
public:
// 构造函数,使用基类构造函数初始化 width 和 height
Triangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重写 area 函数,计算三角形面积
int area() override {
cout << "Triangle class area: " << endl;
return (width * height / 2);
}
};
Comportamiento polimórfico en la función principal:
- Se define un puntero de clase base
shape, este puntero puede apuntar a cualquierShapeobjeto de la clase o de sus clases derivadas. - Primero se hace que el
shapepuntero apunte alRectangleobjetorec, y luego se llama ashape->area(). Dado quearea()es una función virtual, en este momento se enlaza dinámicamente aRectangle::area(), y se muestra el área del rectángulo. - A continuación, se hace que el
shapepuntero apunte alTriangleobjetotri, y al llamar ashape->area()se enlazará dinámicamente aTriangle::area(), y se mostrará el área del triángulo.
// 主函数
int main() {
Shape *shape; // 基类指针
Rectangle rec(10, 7); // 矩形对象
Triangle tri(10, 5); // 三角形对象
// 将基类指针指向矩形对象,并调用 area 函数
shape = &rec;
cout << "Rectangle Area: " << shape->area() << endl;
// 将基类指针指向三角形对象,并调用 area 函数
shape = &tri;
cout << "Triangle Area: " << shape->area() << endl;
return 0;
}
Conceptos clave
Función virtual: En la clase base
Shapese define la función virtualarea(). La función virtual permite que la clase derivada pueda sobrescribir esta función y, en tiempo de ejecución, llamar a la implementación adecuada según el tipo real del objeto al que apunta el puntero.Enlace dinámico: Dado que
area()es una función virtual,shape->area()al llamarla, en tiempo de ejecución, segúnshapeel tipo real del objeto al que apunta (RectangleoTriangle), se llama a la correspondientearea()implementación. Este mecanismo de decidir en tiempo de ejecución qué función llamar se denomina enlace dinámico, y es el núcleo del polimorfismo.Polimorfismo del puntero de clase base: El puntero de clase base
shapepuede apuntar a cualquier objeto derivado deShape. Cuandoshapeapunta a diferentes objetos de clases derivadas, la llamada ashape->area()produce comportamientos diferentes, lo que refleja las características del polimorfismo.- En la clase base puede haber una implementación. Normalmente, la función virtual proporciona una implementación predeterminada en la clase base, pero la subclase puede optar por sobrescribirla.
- Enlace dinámico: En tiempo de ejecución, se llama a la versión correspondiente de la función según el tipo real del objeto.
- Sobrescritura opcional: La clase derivada puede sobrescribir la función virtual de forma opcional, pero no es obligatorio.
- Debe declararse en la clase base como
= 0, lo que indica que no tiene implementación y la subclase debe sobrescribirla. - Clase abstracta: Una clase que contiene funciones virtuales puras no se puede instanciar directamente; solo se puede crear un objeto cuando la clase derivada implementa todas las funciones virtuales puras.
- Definición de interfaz: Las funciones virtuales puras se utilizan generalmente para definir interfaces y permitir que las clases derivadas implementen comportamientos concretos.
Funciones virtuales
Una función virtuales una función declarada en la clase base con la palabra clavevirtualvirtual.
La función virtualpermite que la subclase la sobrescriba, de modo que en tiempo de ejecución, a través de un puntero o referencia de la clase base, se llame a la versión sobrescrita de la subclase, logrando así el enlace dinámico.
Lo que queremos es que en cualquier punto del programa se pueda elegir la función a llamar según el tipo de objeto al que se llama. Esta operación se conoce comoenlace dinámico, oenlace tardío.
Características:
Ejemplo
using namespace std;
class Animal {
public:
virtual void sound() { // Función virtual
cout << "Animal makes a sound" << endl;
}
};
class Dog : public Animal {
public:
void sound() override { // Sobrescribir la función virtual
cout << "Dog barks" << endl;
}
};
int main() {
Animal *animal = new Dog();
animal->sound(); // Salida: Dog barks
delete animal;
}
En el código anterior, sound es una función virtual de la clase Animal. Al llamar a sound() mediante el puntero Animal* animal, el programa elige llamar a Dog::sound() según el tipo real del objeto (Dog).
Funciones virtuales puras
Es posible que desee definir una función virtual en la clase base para redefinirla en la clase derivada y que se adapte mejor al objeto, pero si no puede dar una implementación significativa de la función virtual en la clase base, entonces se utiliza una función virtual pura.
Una función virtual pura es una función virtual sin implementación, que se declara en la clase base con= 0para declararla.
Una función virtual pura indica que la clase base define una interfaz, pero la implementación concreta corre a cargo de la clase derivada.
Una función virtual pura convierte a la clase base en una clase abstracta (abstract class), que no se puede instanciar.
Características:
Podemos reescribir la función virtual area() de la clase base de la siguiente manera:
= 0Le dice al compilador que la función no tiene cuerpo; la función virtual anterior es unafunción virtual pura。
Comparación entre funciones virtuales y funciones virtuales puras
| Característica | Función virtual (Virtual Function) | Función virtual pura (Pure Virtual Function) |
|---|---|---|
| Definición | En la clase base se usavirtualvirtual, y tiene implementación | En la clase base se usa= 0= 0, y no tiene implementación |
| Sobrescritura en subclase | La subclase puede elegir sobrescribirla | La subclase debe implementarla |
| Abstracción | La clase se puede instanciar | Convierte la clase en abstracta, no se puede instanciar |
| Uso | Proporciona un comportamiento predeterminado y permite que la subclase lo sobrescriba | Define una interfaz y obliga a la subclase a implementar un comportamiento concreto |