HERENCIA MÚLTIPLE

Es la propiedad por la cual una clase derivada puede tener más de una clase base.

La herencia múltiple es un concepto que encierra cierto grado de complejidad para el compilador (y también para el lenguaje). Recién en la versión 2.0 de C++ se implementa.

Sintaxis:

class Nombre (clase derivada) : <especificador de Acceso> Nombre 1
				<especificador de Acceso> Nombre 2
				...
				<especificador de Acceso> Nombre n
{    cuerpo de la clase
}

Ejemplo de herencia múltiple:

Grabar este ejemplo

#include<iostream.h>
class Reloj { int hora; int minuto; int segundo; public: Reloj(int h=0, int m=0, int s=0); void DefineTiempo(int, int, int); void DefineHora(int); void DefineMinuto(int); void DefineSegundo(int); int Hora(void); int Minuto(void); int Segundo(void); void IncTiempo(void); };
Reloj::Reloj(int h, int m, int s) { hora=h; minuto=m; segundo=s; }
void Reloj::DefineTiempo(int h, int m, int s) { hora=h; minuto=m; segundo=s; }
void Reloj::DefineHora(int h) { hora = h; }
void Reloj::DefineMinuto(int m) { minuto = m; }
void Reloj::DefineSegundo(int s) { segundo = s; }
int Reloj::Hora(void) { return hora; }
int Reloj::Minuto(void) { return minuto; }
int Reloj::Segundo(void) { return segundo; }
void Reloj::IncTiempo(void) { if (segundo < 59) segundo++; else if(minuto<59) { minuto++; segundo = 0; } else if (hora<23) { hora++; minuto = segundo = 0; } else hora = minuto = segundo = 0; }
enum bool {false,true}; enum TipoDeBanda {AM, FM};
class Radio { float frecuencia; TipoDeBanda banda; bool prendido; public: Radio(float f=95.5, TipoDeBanda b=FM, bool p=false); void CambiaBanda(TipoDeBanda); void CambiaFrecuencia(float); void On_Off(void); float Frecuencia(void); TipoDeBanda Banda(void); bool Prendido(void); };
Radio::Radio(float f, TipoDeBanda b, bool p) { frecuencia = f; banda = b; prendido = p; }
void Radio::CambiaBanda(TipoDeBanda b) { banda = b; }
void Radio::CambiaFrecuencia(float f) { frecuencia = f; }
void Radio::On_Off(void) { prendido= bool(!prendido); }
float Radio::Frecuencia(void) { return frecuencia; }
TipoDeBanda Radio::Banda(void) { return banda; }
bool Radio::Prendido(void) { return prendido; }
enum TipoAlarma {Musica, Timbre};
class RadioReloj: public Radio, public Reloj { int alarmaHora; int alarmaMinuto; int alarmaSegundo; TipoAlarma tipo; bool alarma; public: RadioReloj(int h=0, int m=0, int s=0, float f=100.1, TipoDeBanda b=FM, bool p=false, int ah=0, int am=0, int as=0, TipoAlarma t=Timbre, bool a=false); void FijaAlarma(int, int, int, TipoAlarma); void On_Off_Alarma(void); int AlarmaHora(void); int AlarmaMinuto(void); int AlarmaSegundo(void); TipoAlarma Tipo(void); bool Alarma(void); bool VerificaAlarma(void); void SuenaAlarma(void); };
RadioReloj :: RadioReloj(int h, int m, int s, float f, TipoDeBanda b, bool p, int ah, int am, int as, TipoAlarma t, bool a): Radio(f,b,p),Reloj(h,m,s) { alarmaHora = ah; alarmaMinuto = am; alarmaSegundo = as; tipo = t; alarma = a; }
void RadioReloj::FijaAlarma(int ah , int am, int as, TipoAlarma t) { alarmaHora = ah; alarmaMinuto = am; alarmaSegundo = as; tipo = t; alarma = true; }
void RadioReloj::On_Off_Alarma(void) { alarma = bool(!alarma); }
int RadioReloj::AlarmaHora(void) { return alarmaHora; }
int RadioReloj::AlarmaMinuto(void) { return alarmaMinuto; }
int RadioReloj::AlarmaSegundo(void) { return alarmaSegundo; }
TipoAlarma RadioReloj::Tipo(void) { return tipo; }
bool RadioReloj::Alarma(void) { return alarma ; }
bool RadioReloj::VerificaAlarma(void) { if (Hora()==alarmaHora && Minuto()==alarmaMinuto && Segundo()==alarmaSegundo) return true; return false; }
void RadioReloj::SuenaAlarma(void) { if (tipo == Timbre || Prendido()) { const char BEEP=7; for (int i =0; i<10; i++) cout<<BEEP; } else { On_Off(); cout << "Frecuencia: " << Frecuencia(); if (Banda()) cout << "FM"<< endl; else cout << "AM"<< endl; } }
void main(void) { RadioReloj casio; int h,m,s, ah,am,as;
cout << "Ingrese la hora acual : "; cin >> h >> m >> s;
casio.DefineTiempo(h,m,s);
cout << "Ingrese la hora de Aviso : "; cin >> ah >> am >> as;
casio.FijaAlarma(ah,am,as,Musica); casio.CambiaFrecuencia(95.5);
while (1) { casio.IncTiempo(); if (casio.VerificaAlarma()) { casio.SuenaAlarma(); break; } } }

Ambigüedad

Cuando una clase hereda las propiedades de dos o más clases y en estas últimas existen elementos que, estando en clases base diferentes, tienen el mismo nombre, el compilador C++ se ve imposibilitado, por sí solo, a utilizar el elemento por no saber cuál código se desea emplear, a esto se conoce con el nombre de "Ambigüedad de Código". Si se diera esta situación en un programa, se debe utilizar el operador de ámbito de alcance con el identificador de la clase base que se desea emplear.

Ejemplo:

Grabar este ejemplo

#include<iostream.h>
class A { int a; public: void Define(int); int Dame(void); };
void A::Define(int x) { a=x; }
int A::Dame(void) { return a; }
class B { int b; public: void Define(int); int Dame(void); };
void B::Define(int x) { b=x; }
int B::Dame(void) { return b; }
class C: public A, public B { int c; public: int f_c(void); };
int C::f_c(void) { // c=Dame(); invalido c=A::Dame()+B::Dame(); return c; }
void main(void) { C CC; CC.A::Define(5); CC.B::Define(12); cout << CC.f_c() << endl; }

Herencia Repetida

Una clase no puede derivar dos veces de la misma clase, esto es:

class A
{    ...
}
class B : public A, public A { //inválido }

Sin embargo, la herencia puede traer como consecuencia que, en forma voluntaria o no, se presenten situaciones como la siguiente:

class A
{  public:
     int a;
     ...
}
class B : public A { ... }
class C : public A { ... }

Esto es normal, dos clases diferentes que derivan de una misma clase, sin embargo, que pasa si luego se define:

class D : public B, public C
{    ...
}

Nos encontraríamos bajo esta jerarquía de clases:

Esta situación es válida.

Uno de los problemas que se presentan bajo este esquema es la ambigüedad, sin embargo, esto se puede solucionar con el operador de resolución de ámbito (::), esto es:

D.B :: a y D.C :: a

Un segundo problema que se puede presentar en que el compilador de C++, para crear un objeto de la clase D, separará un espacio doble de memoria para los elementos de A, ya que B está constituido por los elementos de A y también C. Esta forma de separar memoria tiene sentido en algunos casos pero en otros no.

Veamos el siguiente ejemplo:

Grabar este ejemplo

class Persona
{    char Nombre [30];
     char Direccion [30];
     char Telefono [8];
     int Edad; 
     char DNI [20];
   public:
     ...
}
class Profesor : public Persona { char GradoAcademico [30]; char Titulo [30]; char Categoria [20]; char Dedicacion [20]; char UltimaPublicacion [30]; public: ... }
class AsistenteDeCatedra : public Profesor { char Especialidad [30]; char CreditosFaltantes; char Categoria [20]; char Dedicacion [20]; ... public: ... }

Si luego se desea modelar un objeto para los cursos de una Universidad en particular que requiera, entre otras cosas, de definir un Profesor y un Asistente de Cátedra, se podría hacer lo siguiente:

class Curso : public Profesor, public AsistenteDeCatedra
{    char NombreDelCurso [30];
     int NumeroDeCreditos;
     char Facultad[30];
   public:
     ...
}

En este curso se justifica la separación doble de memoria ya que tanto el Profesor como el Asistente de Cátedra requieren, por separado, la información que define Persona para ser distinguidos.

Sin embargo, si se define la clase:

class Investigador : public Persona
{    char AreaDeInvestigacion [30];
     char MayorDistincionObtenida [30];
     char UltimoTrabajoDeInvestigacion [30];
     ...
   public:
     ...
}

Y luego queremos modelar una clase que defina a un Profesor que hace a su vez Investigaciones, el definir la clase de la siguiente forma:

class ProfesorInvestigador : public Profesor, public Investigador
{    char Categoria [30];
     ...
}

En este caso, no se puede permitir una duplicidad de memoria, debido a que tanto el profesor como el investigador se refieren a los mismos datos definidos en persona.

Para resolver este problema, evitando esta duplicidad, se emplea la palabra reservada "virtual", de la siguiente manera:

class Persona 	//no cambia
{    ...
}
class Profesor : virtual public Persona { ... } class Investigador : virtual public Persona { ... }

Luego al hacer:

class ProfesorInvestigador : public Profesor, public Investigador
{
}

Se habrá definido sólo un espacio para los datos de Profesor, con esto se elimina cualquier ambigüedad ya que siempre se estará refiriendo a la misma persona. La jerarquía para este modelo sería:

Orden de llamada de los Constructores

Observe el siguiente programa:

Grabar este ejemplo

#include<iostream.h>
class A { int A1; public: A(int n1=0); void DefineA1(int); int DameA1(void); };
A::A(int n1) { A1=n1; }
void A::DefineA1(int n) { A1=n; }
int A::DameA1(void) { return A1; }
//Herencia Repetida simple
class B : public A
{    int B1;  
   public:    
     B(int na=0, int nb=0);     
     void DefineB1(int);  
     int DameB1(void);             
};
B::B(int na, int nb) : A(na) { B1=nb; }
void B::DefineB1(int n) { B1=n; }
int B::DameB1(void) { return B1; }
class C : public A
{    int C1;
   public:
     C(int na=0, int nc=0);
     void DefineC1(int);
     int DameC1(void);
};
C::C(int na, int nc) : A(na) { C1=nc; }
void C::DefineC1(int n) { C1=n; }
int C::DameC1(void) { return C1; }
class D : public B, public C { int D1; public: D(int na=0, int nb=0, int nc=0, int nd=0); void DefineD1(int); int DameD1(void); };
D::D(int na, int nb, int nc, int nd) : B(na, nb), C(na+1,nc) { D1=nd; }
void D::DefineD1(int n) { D1=n; }
int D::DameD1(void) { return D1; }
//Herencia Repetida con clases virtuales
class Bv : public virtual A
{    int Bv1;                
   public:                   
     Bv(int na=0, int nb=0); 
     void DefineBv1(int);    
     int DameBv1(void);      
};
Bv::Bv(int na, int nb) : A(na) { Bv1=nb; }
void Bv::DefineBv1(int n) { Bv1=n; }
int Bv::DameBv1(void) { return Bv1; }
class Cv : public virtual A
{    int Cv1;
   public:
     Cv(int na=0, int nc=0);
     void DefineCv1(int);
     int DameCv1(void);
};
Cv::Cv(int na, int nc) : A(na) { Cv1=nc; }
void Cv::DefineCv1(int n) { Cv1=n; }
int Cv::DameCv1(void) { return Cv1; }
class Dv : public Bv, public Cv { int Dv1; public: Dv(int na=0, int nb=0, int nc=0, int nd=0); void DefineDv1(int); int DameDv1(void); };
Dv::Dv(int na, int nb, int nc, int nd) : Bv(na, nb), Cv(na+1,nc) { Dv1=nd; }
void Dv::DefineDv1(int n) { Dv1=n; }
int Dv::DameDv1(void) { return Dv1; }
void main (void) { D ObjD(1,2,3,4); cout << "Datos del Objeto D - Herencia repetida simple" << endl; cout << "D = " << ObjD.DameD1() << endl; cout << "C = " << ObjD.DameC1() << endl; cout << "B = " << ObjD.DameB1() << endl; cout << "A-C = " << ObjD.C::DameA1() << endl; cout << "A-B = " << ObjD.B::DameA1() << endl; cout << endl <<endl;
Dv ObjDv(1,2,3,4); cout << "Datos del Objeto D - Herencia repetida virtual" << endl;
cout << "Dv = " << ObjDv.DameDv1() << endl; cout << "Cv = " << ObjDv.DameCv1() << endl; cout << "Bv = " << ObjDv.DameBv1() << endl; cout << "Av-Cv = " << ObjDv.Cv::DameA1() << endl; cout << "Av-Bv = " << ObjDv.Bv::DameA1() << endl; cout << "A = " << ObjDv.DameA1() << endl;
ObjDv.Cv::DefineA1(5); cout << "Av-Cv = " << ObjDv.Cv::DameA1() << endl; cout << "A = " << ObjDv.DameA1() << endl;
ObjDv.Bv::DefineA1(15); cout << "Av-Bv = " << ObjDv.Bv::DameA1() << endl; cout << "Av-Cv = " << ObjDv.Cv::DameA1() << endl; cout << "A = " << ObjDv.DameA1() << endl; }
/* Datos del Objeto D - Herencia repetida simple D = 4 C = 3 B = 2 A-C = 2 A-B = 1
Datos del Objeto D - Herencia repetida virtual Dv = 4 Cv = 3 Bv = 2 Av-Cv = 0 Av-Bv = 0 A = 0 Av-Cv = 5 A = 5 Av-Bv = 15 Av-Cv = 15 A = 15 */

El compilador C++ utiliza el siguiente orden de inicialización:

  1. Todas las clases base virtuales se inicializan primero, es decir sus constructores se ejecutan en primer lugar.

    En el ejemplo anterior se aprecia:

    AV - CV = 0
    AV - BV = 0
    A = 0

    Esto se debe a que al definir el objeto ObjDv se ejecuta el constructor de A, como no le llegan los argumentos, inicializa A1 en 0 (por defecto), luego se pasan los argumentos, como un constructor se ejecuta una sola vez, los valores puestos en la definición del objeto no tienen efecto para A1.

  2. Las clases no virtuales se ejecutan en el orden en que aparecen en una declaración de clases, es decir, se ejecutan a continuación de los de las clases virtuales.

  3. Se ejecutan los constructores de las clases derivadas en el orden en que aparecen en la declaración de la clase.



Volver a contenidos AtrásSiguiente