Herencia múltiple

La herencia múltiple se encuentra en aquellos casos en los que una clase derivada tiene varias clases base (al menos, más de una).
Un ejemplo típico sería una clase ProfesorUniversitario que heredara de las clases Profesor e Investigador.
La clase Profesor declararía la información que deseamos conocer sobre los profesores: su nombre y la materia que imparten.
class Profesor {
private:
string nombre;
string materia;
public:
Profesor(const string &n, const string &m) : nombre( n ), materia( m )
{}
const string &getNombre() const
{ return nombre; }
const string &getMateria() const
{ return materia; }
};
La clase Investigador declararía la información que deseamos conocer sobre los investigadores: su nombre y la especialidad en la que investigan.
class Investigador {
private:
string nombre;
string especialidad;
public:
Investigador (const string &n, const string &e) : nombre( n ), especialidad( e )
{}
const string &getNombre() const
{ return nombre; }
const string &getEspecialidad() const
{ return especialidad; }
};
La forma de crear la clase mediante herencia múltiple sería la siguiente:
class ProfesorUniversitario : public Profesor, public Investigador {
public:
ProfesorUniversitario(const string &n, const string &e, const string &m)
: public Profesor( n, m ), public Investigador( n, e )
{}
};
En principio, no es necesario nada más en la clase ProfesorUniversitario, ya que todo lo que necesitamos para él viene dado por las clases superiores. Podríamos visualizar la información de un ProfesorUniverstiario de la siguiente manera:
int main()
{
ProfesorUniversitario p( "Baltasar", "Máquinas Virtuales", "Tecnología de Objetos" );

cout << p.getNombre() << ','
<< p.getMateria() << ','
<< p.getEspecialidad() << endl
;
}
Sin embargo, el programa anterior tiene múltiples defectos, como que el manejo que realiza de la memoria es altamente ineficiente. Esto es debido a que hay dos atributos nombre que se emplean en ProfesotrUniversitario, el heredado de Investigador y el heredado de Profesor.
Además, no compila, ya que cuando se trata de compilar p.getNombre() ... ¡no se sabe cuál utilizar! ... ¿el de Investigador o el de Profesor?. La única forma de solucionar ésto sería cualificar el método a invocar con el nombre de la clase, por ejemplo: p.Investigador::getNombre()

Herencia en diamante

Siguiendo con el ejemplo anterior, se podría pensar en crear una clase común inicial Persona, que fuese la que proporcionara el nombre, y de esta manera tenerlo una sola vez en toda la jerarquía:

class Persona {
private:
string nombre;
public:
const string &getNombre() const
{ return nombre; }
};

Ahora sólo sería necesario que las clases Profesor e Investigador heredasen de Persona:

class Profesor : public Persona {
// más cosas ...
Profesor(const string &n, const string &m) : Persona( n )
{ materia = m; }

};

class Investigador : public Persona {
// más cosas ...
public:
Investigador(const string &n, const string &e) : Persona( n )
{ especialidad = e; }
};

En principio, ahora ya no sería necesario ningún truco para que la llamada a getNombre() de un objeto ProfesorUniversitario funcionara ...

ProfesorUniversitario p( "Baltasar", "Tecnología de Objetos", "Máquinas virtuales" );
cout << p.getNombre() << endl;

Sin embargo, no lo hace. De hecho, proporciona un mensaje de error muy extraño (en GNU g++):
getNombre is ambiguous
candidates are:
Persona::getNombre
Persona::getNombre
¿Qué está pasando?
Aunque se buscaba una herencia en diamante, es decir, una jerarquía en la que la clase base es Persona, de la que heredan Profesor e Investigador, y de la que a su vez hereda ProfesorUniversitario (de ahí el nombre de este tipo de herencia (tiene nombre, precisamente debido a este problema)), lo que se ha obtenido en cambio, es que Profesor e Investigador tienen cada uno su propia copia propia de la clase Persona como clase base, lo cual explica el mensaje de error obtenido. La razón de esto no es otra que una cuestión de diseño del lenguaje C++.

Se puede "arreglar" este problema de la siguiente forma:
class Profesor : public virtual Persona {
// más cosas ...
Profesor(const string &n, const string &m) : Persona( n )
{ materia = m; }

};
class Investigador : public virtual Persona {
// más cosas ...
public:
Investigador(const string &n, const string &e) : Persona( n )
{ especialidad = e; }
};
class ProfesorUniversitario : public Persona, public Investigador {
public:
ProfesorUniversitario(const string &n, const string &m, const string &e)
: Persona( n ), Investigador( n, e ), Profesor( n, m )
{}

};
Como se puede apreciar, la solución es bastante poco elegante y engorrosa. Hay que poner virtual delante de la herencia de Persona en las clases Profesor e Investigador. Además, la clase ProfesorUniversitario debe también inicializar la clase Persona, pues de otra forma el lenguaje no se ve capaz de optar por una rama (herencia por profesor o investigador) para llegar hasta Persona e inicializarla.

Llegados hasta este punto, se torna claro que la herencia múltiple puede parecer beneficiosa en un primer término, pero produce muchos más problemas de los que soluciona en un principio. Por ello se desaconseja su uso, y lenguajes modernos como Java y C# ya no la soportan.