Polimorfismo

Para poder explicar más claramente qué es el polimorfismo, y para qué sirve, usaremos un ejemplo que se irá desarrollando poco a poco, concretamente uno bastante conocido, que es el de las figuras.

Las figuras y la clase Figura

Supongamos la creación de dos clases, para representar figuras, Rectangulo y Circulo:
class Rectangulo {
private:
double lado1;
double lado2;
public:
Rectangulo(double l1, double l2) : lado1( l1 ), lado2( l2 )
{}
double calcularArea()
{ return lado1 * lado2; }
};

class Circulo {
private:
double radio;
public:
Circulo(double r) : radio( r )
{}
double calcularArea()
{ return radio * radio * 3.1415927; }
};
Estas clases están bien, pero por ejemplo, sería útil poder representar a estas clases mediante una sola clase (es decir, un sólo concepto). El principal propósito de la herencia es la clasificación, así que ...
class Figura {};
Es necesario modificar las clases de manera que:

class Rectangulo : public Figura {
private:
double lado1;
double lado2;
public:
Rectangulo(double l1, double l2) : lado1( l1 ), lado2( l2 )
{}
double calcularArea()
{ return lado1 * lado2; }
};

class Circulo : public Figura {
private:
double radio;
public:
Circulo(double r) : radio( r )
{}
double calcularArea()
{ return radio * radio * 3.1415927; }
};

Ahora, sería posible referencia a un rectángulo o un círculo mediante la clase Figura.

Figura * f = new Rectangulo( 7, 6 );
Figura * f2 = new Circulo( 6 );
delete f;
delete f2;

Desafortunadamente, no es posible hacer nada con esta nueva clase, ya que no nos proporciona ninguna información sobre las clases derivadas. Podríamos realizar una intentona de este tipo:

class Figura {

public:

double calcularArea() { return 0; }
};


Sin embargo, si hacemos:


Figura * f = new Rectangulo( 7, 6 );
Figura * f2 = new Circulo( 6 );
cout << f->calcularArea() << endl;
cout << f2->calcularArea() << endl;
delete f;
delete f2;
... la respuesta será de cero en ambos casos ...

Polimorfismo

El problema, es que al contrario que en otros lenguajes (como Java), en C++ los métodos no son polimórficos por defecto. Es decir, para un puntero de la clase Figura se ejecutará el método calcularArea() de la clase Figura, sin tener en cuenta el objeto al que realmente apunta el puntero.

Si deseamos una llamada polimórfica, en C++ debemos marcar el método como 'virtual':

class Figura {
public:
virtual double calcularArea()
{ return 0; }
};

De esta forma, las llamadas funcionan correctamente.
En realidad, no precisamos de un cuerpo para este método, ya que, al fin y al cabo, donde realmente sabremos cómo calcular el área es en las figuras Rectángulo y Círculo.
Podemos solventarlo así:
class Figura {
public:
virtual double calcularArea() = 0;

};

Además, ahora obtenemos dos beneficios extra: no es posible crear objetos de la clase Figura (lo cuál, es lógico, por otra parte), y al ser el método abstracto (no tiene cuerpo), obligatoriamente las clases derivadas deben redefinirlo (lo cuál es precisamente lo que deseamos).

En realidad, el polimorfismo está íntimamente ligado a la ligadura tardía. No puede haber una sin otra, ni la otra sin la una.

Ligadura tardía

La ligadura tardía (o enlace dinámico), significa que se espera a tiempo de ejecución para enlazar correctamente la llamada a un método con el método de la clase correspondiente al objeto.
Veamos un ejemplo:
using namespace std;

int main()
{
int opcion;
Figura * f;
string entrada;

cout << "1.Rectángulo\n2.Círculo\nOpción:" << endl;
getline( cin, entrada );
opcion = atoi( entrada.c_str() );

if ( opcion == 1 )
f = new Rectangulo( 5, 6 );
else f = new Circulo( 7 );

cout << "\nEl àrea es: " << f->calcularArea() << endl; // enlace dinámico

return 0;
}
En la línea comentada, la llamada a f->calcularArea() no puede enlazarse con Rectangulo::calcularArea() o Circulo::calcularArea() hasta tiempo de ejecución, momento en el que se sabrá, tras la entrada del usuario (la elección de la opción del menú), a qué objeto apunta f.