Translate

domingo, 15 de junio de 2025

¿Por qué C++ necesita punteros para implementar polimorfismo?


En muchos lenguajes de programación orientados a objetos, como Java, C#, Python o Ruby, el polimorfismo funciona de forma "natural" al manejar los objetos por referencia. Sin embargo, cuando trabajamos con C++, rápidamente notamos un detalle que desconcierta a muchos: para obtener polimorfismo dinámico necesitamos punteros (o referencias). ¿Por qué ocurre esto? Vamos a desglosarlo.

El polimorfismo permite invocar métodos sobre objetos de clases derivadas a través de una referencia o puntero a la clase base. El método invocado es el de la clase real del objeto, no necesariamente el de la clase base.

En C++, esto se logra declarando métodos virtual en la clase base:


class Animal {

public:

    virtual void hablar() {

        std::cout << "Soy un animal" << std::endl;

    }

};


class Perro : public Animal {

public:

    void hablar() override {

        std::cout << "Guau" << std::endl;

    }

};


Ahora viene la parte clave.


Animal a = Perro();

a.hablar();  // Imprime: "Soy un animal"


A pesar de haber creado un Perro, el método hablar de Animal es el que se llama. ¿Por qué?

Porque en esta asignación:


Animal a = Perro();


se produce slicing ("corte de objeto"). Solo la parte Animal del Perro es copiada en el objeto a. El comportamiento dinámico queda perdido.


Perro p;

Animal* a = &p;

a->hablar();  // Imprime: "Guau"


Ahora sí, el método correcto se invoca. ¿Qué cambió? Simple: no hay slicing. Estamos trabajando con la dirección de memoria del objeto original, donde toda la jerarquía de clases está intacta.

La razón está en el modelo de objetos de C++:

  • C++ permite que los objetos vivan por valor. Si asignamos Animal a = Perro(), se copia sólo la parte visible desde Animal. Las partes de Perro no existen en esa copia.
  • Los lenguajes como Java o C# manejan los objetos siempre por referencia; nunca se copia el objeto en una asignación como `Animal a = new Perro()`. Por eso ahí el slicing no ocurre.


Cuando usamos punteros o referencias en C++:

  • No se copia el objeto.
  • Se mantiene la dirección al objeto original.
  • El compilador puede usar el vtable (tabla virtual) para despachar la llamada al método correcto.

Una de las ventajas de C++ es que te permite elegir:

  • Si quieres performance máxima (sin polimorfismo), puedes trabajar por valor.
  • Si quieres polimorfismo dinámico, debes trabajar por puntero o referencia.


Este control es una de las razones por las cuales C++ sigue siendo muy usado en sistemas embebidos, motores de juego, drivers, etc.

C++ también permite otras formas de polimorfismo (estático), como el basado en plantillas (CRTP, SFINAE, concepts en C++20), donde no necesitamos punteros porque el binding ocurre en tiempo de compilación.