Herencia en C++: Conceptos Avanzados

Herencia y Relaciones de Amistad

Las relaciones de amistad no se heredan. Una función amiga declarada en la clase base no tiene acceso a los miembros privados o protegidos de las clases derivadas.

Herencia con Miembros Estáticos

Cuando una clase base define un miembro estático, existe una única instancia de ese miembro en toda la jerarquía de herencia, compartida por todas las clases derivadas.

class Usuario
{
public:
    Usuario() { ++totalUsuarios; }
protected:
    string nombreCompleto;
public:
    static int totalUsuarios;
};

int Usuario::totalUsuarios = 0;

class Cliente : public Usuario
{
protected:
    string numeroIdentificacion;
};

int main()
{
    Usuario u1;
    Cliente c1;

    cout << &Usuario::totalUsuarios << endl;
    cout << &Cliente::totalUsuarios << endl;

    return 0;
}

Modalidades de Herencia

Herencia Simple

Una clase derivada tiene una única clase base directa.

Herencia Múltiple

Una clase derivada puede heredar de dos o más clases base directas.

Herencia Diamante

Es un caso especial de herencia múltiple donde una clase hereda de dos clases que, a su vez, comparten una misma clase base común.

Este esquema genera problemas de redundancia de datos y ambigüedad. Al instanciar un objeto de la clase más derivada, el acceso a miembros heredados se vuelve ambiguo.

class Persona
{
public:
    string nombre;
};

class Estudiante : public Persona
{
protected:
    int matricula;
};

class Profesor : public Persona
{
protected:
    int idEmpleado;
};

class Becario : public Estudiante, public Profesor
{
protected:
    string areaInvestigacion;
};

void Ejemplo()
{
    Becario b;
    // Esto genera ambigüedad: ¿cuál 'nombre' heredado?
    b.nombre = "Ana";
}

Para resolver la ambigüedad, podemos usar el operador de ámbito para especificar la ruta de herencia deseada:

b.Estudiante::nombre = "Ana";
b.Profesor::nombre = "Ana";

Sin embargo, esto no resuelve la redundancia: el objeto Becario sigue conteniendo dos copias distintas del miembro nombre.

Herencia Virtual para Resolver el Problema Diamante

La herencia virtual permite que múltiples clases que heredan de una misma base compartan una única copia de esa base.

class Persona
{
public:
    string nombre;
};

class Estudiante : virtual public Persona
{
protected:
    int matricula;
};

class Profesor : virtual public Persona
{
protected:
    int idEmpleado;
};

class Becario : public Estudiante, public Profesor
{
protected:
    string areaInvestigacion;
};

void Ejemplo()
{
    Becario b;
    b.nombre = "Carlos"; // Ahora es único y no ambiguo
}

Principio de Funcionamiento de la Herencia Virtual

Para entender cómo se resuelve la redundancia, consideremos primero un caso de herencia diamante sin virtual:

class Base
{
public:
    int datoBase;
};

class Derivada1 : public Base
{
public:
    int datoDeriv1;
};

class Derivada2 : public Base
{
public:
    int datoDeriv2;
};

class Final : public Derivada1, public Derivada2
{
public:
    int datoFinal;
};

int main()
{
    Final obj;
    obj.Derivada1::datoBase = 10;
    obj.Derivada2::datoBase = 20;
    obj.datoDeriv1 = 30;
    obj.datoDeriv2 = 40;
    obj.datoFinal = 50;
    return 0;
}

El objeto de clase Final contiene dos copias del datoBase, una heredada por cada rama.

Ahora, aplicando herencia virtual:

class Base
{
public:
    int datoBase;
};

class Derivada1 : virtual public Base
{
public:
    int datoDeriv1;
};

class Derivada2 : virtual public Base
{
public:
    int datoDeriv2;
};

class Final : public Derivada1, public Derivada2
{
public:
    int datoFinal;
};

int main()
{
    Final obj;
    obj.Derivada1::datoBase = 10;
    obj.Derivada2::datoBase = 20; // Sobrescribe la misma ubicación
    obj.datoDeriv1 = 30;
    obj.datoDeriv2 = 40;
    obj.datoFinal = 50;
    return 0;
}

En este modelo, el miembro de la base común se coloca una sola vez en la parte inferior del objeto Final. Las clases Derivada1 y Derivada2 contienen un puntero a una tabla virtual (vtable) que almacena el desplazamiento (offset) necesario para localizar esa instancia única de la base común.

Preguntas Técnicas sobre Herencia Diamante

  1. ¿Qué es la herencia diamante y cuál es su problema principal?
    Es un esquema donde dos clases derivan de una misma base, y una tercera clase deriva de ambas. El problema es la redundancia de datos (dos copias del miembro heredado de la base común) y la ambigüedad en el acceso a esos miembros.
  2. ¿Cómo funciona la herencia virtual para solucionar estos problemas?
    Se utiliza el especificador virtual en las clases intermedias. Esto garantiza que la clase base común se herede una sola vez en la jerarquía. Se emplean punteros a tablas virtuales y offsets para resolver el acceso a la base única.
  3. ¿Cuándo usar herencia y cuándo usar composición?
    La herencia establece una relación "es un" (is-a). La composición establece una relación "tiene un" (has-a). Use herencia cuando una clase sea un tipo específico de otra. Use composición cuando una clase contenga o utilice otra como parte de su implementación.

Etiquetas: C++ herencia herencia-virtual diamante-multiple multiples-bases

Publicado el 7-19 07:13