You are on page 1of 29

4.11.

2a2 Sobrecarga de mtodos


1 Sinopsis
Lo mismo que ocurre con las funciones normales (
4.4.1), en la definicin de funciones-miembro
puede ocurrir que varias funciones compartan el mismo nombre, pero difieran en el tipo y/o nmero
de argumentos. Ejemplo:
class hotel {
char * nombre;
int capacidad;
public:
void put(char*);
void put(long);
void put(int);
}

La funcin put, definida tres veces con el mismo nombre est sobrecargada; el compilador sabe
en cada caso qu definicin usar por la naturaleza de los argumentos (puntero-a-char, long o int).
Nota: no confundir estos casos de sobrecarga en mtodos con el polimorfismo; este ltimo
se refiere a jerarquas de clases en las que pueden existir diversas definiciones de mtodos en
miembros de distintas generaciones (super-clases y clases derivadas) con los mismos
argumentos. Son las denominadas funciones virtuales (
4.11.8a).

2 Adecuacin automtica de argumentos


Una caracterstica interesante, y a nuestro entender muy desafortunada del lenguaje (por la
posibilidad de errores difciles de detectar), es que en la invocacin de funciones, sean estas
miembros de clase o no, el compilador automticamente intenta adecuar el tipo del argumento
actual con el formal (
4.4.5). Es decir, adecuar el argumento utilizado con el que espera la
funcin segn su definicin.
Este comportamiento puede esquematizarse como sigue:
int funcion (int i) { /* ... */
....
otraFuncion (){
int x = funcion(5);
int y = funcion(int('a'));
int z = funcion('a');
...
}

// L.1:

// L.4: Ok. concordancia de argumentos


// L.5: Ok. promocin explcita
// L.6: Ok. promocin automtica!!

Es evidente que en L.4 y L.5 los argumentos formales y los actuales concuerdan, bien porque se
trata del mismo tipo, bien porque se ha realizado una promocin explcita (
4.9.9).

Lo que no resulta ya tan evidente, ni deseable a veces, es que en L.6 el compilador autorice la
invocacin a funcion pasando un tipo distinto del esperado. Esta promocin del argumento actual
realizada de forma automtica por el compilador (sin que medie ningn error o aviso -"Warning"- de
compilacin) puede ser origen de errores, y de que el programa use inadvertidamente una funcin
distinta de la deseada. En este sentido podemos afirmar que el lenguaje C++ es dbilmente tipado.
Posiblemente esta caracterstica sea una de las desafortunadas herencias del C clsico [1], y es
origen incluso de ambigedades que no deberan existir. Por ejemplo, no es posible definir las
siguientes versiones de un constructor (
4.11.2d):
class Entero {
public: int x;
Entero(float fl = 1.0) { x = int(fl); }
Entero(char c) { x = int(c); }
}
A pesar de su evidente disparidad, el compilador interpreta que existe ambigedad en los tipos, ya
que char puede ser provomido a float.

2.1 Para complicar la cosa


Pero la conformacin de argumentos no acaba con lo expuesto. Si en la invocacin de una
funcin f1 no existe correspondencia entre el tipo de un argumento actual x, y el formal y, el
compilador puede buscar automticamente en el mbito si existe una funcin f2 que acepte el
argumento discordante x y devuelva el tipo y deseado. En cuyo caso realizar una invocacin
implcita a dicha funcin f2(x) y aplicar el resultado y como argumento a la funcin primitiva. El
mecanismo involucrado puede ser sintetizado en el siguiente esquema:
class C {
// L.1
public: int x;
...
};
int getx (C c1, C c2) { return c1.x + c2.x; }
C fun (float f= 1.0) {
// L.6:
C ct = { f };
return ct;
}
...
unaFuncion () {
int x = 10;
C c1;
...
int z = getx(x, c1);
// L.15
...
}

// L.5

La invocacin a getx en L.15 es correcta, a pesar de no existir una definicin concordante para
esta funcin cuyos argumentos formales son dos objetos tipo C, y en este caso el primer
argumento x es un int. El mecanismo utilizado por el compilador es el siguiente:

Dentro del mbito de visibilidad existe una funcin fun (L.6) que acepta un float y devuelve un
objeto del tipo necesitado por getx. Aunque el argumento x disponible no es precisamente
un float, puede ser promovido fcilmente a dicho tipo. En consecuencia el compilador utiliza en
L.15 la siguiente invocacin:
int z = getx( fun( float(x) ), c1);

Este tipo de adecuaciones automticas son realizadas por el compilador tanto con funcionesmiembro [2] como con funciones normales. Considere cuidadosamente el ejemplo que sigue, e
intente justificar el proceso seguido para obtener cada uno de sus resultados.
#include <iostream>
using namespace std;
int x = 10;
long lg = 10.0;
class Entero {
public:
int x;
int getx (int i) { return x * i; }
int getx () { return x; }
int getx (Entero e1, Entero e2) { return e1.x + e2.x; }
Entero(float f= 1.0) { x = f; }
};
int getx (int i) { return x * i; }
int getx () { return x; }
int getx (Entero e1, Entero e2) { return e1.getx() + e2.getx(); }
Entero fun (float f= 1.0) {
Entero e1 = { f }; return e1;
}
void main () {
// ==========================
Entero c1 = Entero(x);
cout << "E1 = " << c1.getx(0) << endl;
cout << "E2 = " << c1.getx() << endl;
cout << "E3 = " << c1.getx('a') << endl;
cout << "E4 = " << c1.getx(lg) << endl;
cout << "E5 = " << c1.getx(x, c1) << endl;
cout << "E6 = " << c1.getx('a', c1) << endl;
cout << "F1 = " << getx(0) << endl;
cout << "F2 = " << getx() << endl;
cout << "F3 = " << getx('a') << endl;
cout << "F4 = " << getx(lg) << endl;
cout << "F5 = " << getx(x, c1) << endl;
cout << "F6 = " << getx('a', c1) << endl;
}
Salida (reformateada en dos columnas):
E1
E2
E3
E4

=
=
=
=

0
10
970
100

F1
F2
F3
F4

=
=
=
=

0
10
970
100

E5 = 20
E6 = 107

F5 = 20
F6 = 107

Temas relacionados

Sobrecarga de funciones (
4.4.1a)
Sobrecarga de funciones genricas (plantillas) 4.12.1-2

4.11.2a3 Clases locales


1 Sinopsis
Las clases C++ pueden ser declaradas dentro de funciones; en cuyo caso se denominan clases
locales. Tanto el identificador como la propia clase pertenecen al mbito de la funcin
contenedora. Ejemplo:
void foo () {
...
class C { ... };
...
C c1;
// Ok.
}
...
C c2;
// Error!! C no es visible

2 La funcin contenedora no tiene especial acceso al mbito de la clase local (salvo el dictado
por las reglas generales):
void foo() {
...
class C {
int x;
public:
char c;
};
C c;
c.x = 10;
c.c = 'X';
}

// Funcin contenedora
// clase local a foo
// privado por defecto

// Error!! el miembro es privado


// Ok.

El error puede ser eliminado con un pequeo cambio que permita la visibilidad de foo en el interior
de la clase:
void foo() {
...
class C {
friend void foo();
int x;
public:
char c;
};

// se permite el acceso

C c;
c.x = 10;
c.c = 'X';

// Ok!! aunque el miembro es privado


// Ok.

3 La clase local solo puede utilizar typenames (


3.2.1e); objetos estticos (
enumeradores (
4.8) del mbito de la funcin contenedora. Ejemplo:

4.11.7) y

void foo() {
static int counter;
enum { KT1 = 33 };
int y = 2;
class C {
int x;
public:
C() {
x = KT1 + counter;
C(int n) {
x = n + y;
}
};

// Ok.
// Error!! y es automtica

4 La clase local no puede contener miembros estticos (


void foo() {
...
class C {
int x;
static int y;
public:
void f1();
static void f2();
...
};
}

4.11.7). Ejemplo:

// Ok.
// Error!!
// Ok.
// ERror!!

Tema relacionado

Clases dentro de clases (

4.13.2)

4.11.2b Herencia simple (I)


1 Sinopsis
A continuacin exponemos otra alternativa a la definicin de clases cuando no se parte desde cero
( 4.11.2a), sino de una clase previamente definida (clase-base o superclase). Esta forma es

conocida como herencia simple; cuando una clase deriva de una antecesora heredando todos sus
miembros (la herencia mltiple es
tratada en
4.11.2c).

Nota: las uniones (


4.6) no
pueden tener clases-base ni
pueden utilizarse como tales.

Es clsico sealar como ejemplo,


que la clase Tringulo deriva de la
clase general Polgono, de la que
tambin derivan las clases
Fig. 1
Cuadrado, Crculo, Pentgono,
etc. (ver figura) [1]. Cualquier tipo
de polgono comparte una serie de propiedades generales con el resto, aunque los tringulos
tienen particularidades especficas distintas de los cuadrados, estos de los pentgonos y de los
crculos, etc. Es decir, unas propiedades (comunes) son heredadas, mientras que otras
(privativas) son especficas de cada descendiente.
Puesto que una clase derivada puede servir a su vez como base de una nueva herencia, se utilizan
los trminos base directa para designar la clase que es directamente antecesora de otra, y base
indirecta para designar a la que es antecesora de una antecesora. En nuestro caso, la clase
Poligono es base directa de Tringulo, y base indirecta de Issceles.
Nota: las denominaciones superclase directa y superclase indirecta tienen respectivamente el
mismo significado que base directa e indirecta.

2 Sintaxis
Cuando se declara una clase D derivada de otra clase-base B, se utiliza la siguiente sintaxis:
class-key nomb-clase : <mod-acceso> clase-base {<lista-miembros>};
<mod-acceso> es un especificador opcional denominado modificador de acceso, que
determina como ser la accesibilidad de los miembros que se heredan de la clase-base en los
objetos de las subclases (
4.11.2b-II).
El significado del resto de miembros es el que se indic al tratar de la declaracin de una clase (
4.11.2), con la particularidad que, en la herencia simple, <: lista-base> se reduce a un solo
identificador: clase-base.
Ejemplo
class Circulo : public Poligono { <lista-miembros> };
En este caso, la nueva clase Circulo hereda todos los miembros de la clase
antecesora Poligono (con las excepciones que se indican a continuacin), pero debido al
modificador de acceso utilizado (public), solo son utilizables los miembros que derivan

de pblicos y protegidos de la superclase Poligono (las cuestiones relativas al acceso se


detallan en la pgina siguiente
Pg. 2).
En este supuesto, resulta evidente que la hipottica funcin mover (en el plano) de la clase crculo
ser distinta de la misma funcin en la clase tringulo, aunque ambas desciendan de la misma
clase polgono. En el primer caso sera suficiente definir las nuevas coordenadas del centro,
mientras que en el segundo habra que definir las nuevas coordenadas de, al menos, dos puntos.

3 Excepciones en la herencia
En principio, una clase derivada hereda todos los miembros (propiedades y mtodos) de la clase
base [2], con las excepciones que siguen de elementos que no pueden heredarse:

Constructores (
4.11.2d1)
Destructores ( 4.11.2d2)
Miembros estticos ( 4.11.7).
Operador de asignacin = sobrecargado (
Funciones friend ( 4.11.2a1).

4.9.18a)

4 Razn de la herencia
Como se ha indicado (
4.11.1), derivar una nueva clase de otra existente, solo tiene sentido si se
modifica en algo su comportamiento y/o su interfaz, y esto se consigue de tres formas no
excluyentes:

Aadiendo miembros (propiedades y/o mtodos), que no existan en la clase base. Estos
nuevos miembros seran privativos de la clase derivada.
Sobrescribiendo mtodos con distintos comportamientos que en la clase primitiva
(sobrecarga).
Redefiniendo propiedades que ya existan en la clase base con el mismo nombre. En este
caso, se crea un nuevo tipo de variable en la clase derivada con el mismo nombre que la
anterior, con el resultado de que coexisten dos propiedades distintas del mismo nombre, la
nueva y la heredada.

Nota: una nueva clase no puede eliminar ningn miembro de sus ancestros. Es decir, no
puede realizarse una herencia selectiva de determinados miembros. Como se ha indicado se
heredan todos; otra cosa es que resulten accesibles u ocultos. Solo en circunstancias muy
particulares de herencia mltiple puede evitarse que se repitan miembros de clases
antecesoras (
4.11.2c1 Herencia virtual).

4.1
Al hilo de estas consideraciones, es importante
resaltar que en una clase derivada existen dos tipos de
miembros (utilizaremos repetidamente esta terminologa):

Heredados: que existen en la clase derivada


simplemente por herencia de la superclase. Este
conjunto de miembros, que existen por herencia de la
superclase, es denominado por Stroustrup subobjeto
de la superclase en el objeto de la clase derivada [5]
Privativos: que existen en la clase derivada porque se
han aadido (no existen en la superclase) o redefinido
(lo que significa a la postre que existen dos versiones
con el mismo nombre; una heredada y otra privativa).

Cuando varias clases son "hermanas" (derivan de una misma


superclase) los miembros heredados soncomunes a todas
ellas, por lo que suelen utilizarse indistintamente ambos
trminos "heredados" y "comunes" para referirse a tales
miembros. La situacin se ha esquematizado en la figura 2 en
la que dos subclases D1 yD2 derivan de un mismo ancestro B.
Cada una tiene sus propios miembros privativos y una copia de
los miembros de su ancestro (esta parte es igual en ambas
clases).

Fig. 2

4.2 Ejemplo
En este ejemplo puede comprobarse que cada instancia de la clase derivada tiene su propio juego
de variables, tanto las privativas como las heredadas; tambin que unas y otras se direccionan del
mismo modo.
#include <iostream.h>
class B {
public: int x;
};
class D : public B {
public: int y;
};
void main() {
B b1;
b1.x =1;
cout << "b1.x = "

// clase raz
// D deriva de B
// y es privativa de la clase D
// ================================
// b1 es instancia de B (clase raz)
<< b1.x

D d1;
d1.x = 2;
d1.y = 3;
D d2;
d2.x = 4;
d2.y = 5;
cout << "d1.x = " <<
cout << "d1.y = " <<
cout << "d2.x = " <<

//
//
//
//

<< endl;

d1 es instancia de D (clase derivada)


este elemento es herdado de la clase padre
este elemento es privativo de d1
otra instancia de D

d1.x << endl;


d1.y << endl;
d2.x << endl;

cout << "d2.y = " << d2.y << endl;


}
Salida:
b1.x
d1.x
d1.y
d2.x
d2.y

=
=
=
=
=

1
2
3
4
5

5 Ocultacin de miembros
Cuando se redefine un miembro heredado (que ya existe con el mismo nombre en la clase base) el
original queda parcialmente oculto o eclipsado para los miembros de la clase derivada y para sus
posibles descendientes. Esta ocultacin se debe a que los miembros privativos dominan sobre los
miembros del subobjeto heredado ( 4.11.2c). Tenga en cuenta que los miembros de las clases
antecesoras que hayan sido redefinidos son heredados con sus modificaciones.
5.1 Ejemplo
#include <iostream.h>
#include <typeinfo.h>
class X { public: int x; };
class Y : public X { public: char x; };
class Z : public Y { };

// Clase raz
// hija
// nieta

void main() {
// =============
X mX;
// instancias de raz, hija y nieta
Y mY;
Z mZ;
cout << "mX.x es tipo: " << typeid(mX.x).name() << endl;
cout << "mY.x es tipo: " << typeid(mY.x).name() << endl;
cout << "mZ.x es tipo: " << typeid(mZ.x).name() << endl;
}
Salida:
mX.x es tipo: int
mY.x es tipo: char
mZ.x es tipo: char

5.2 Cuando los miembros redefinidos son funciones


Los miembros de las clases antecesoras que pueden ser redefinidos en las clases derivadas no
solo pueden ser propiedades (como en el ejemplo anterior), tambin pueden ser mtodos.
Considere el siguiente ejemplo [4]:
#include <iostream.h>
class Cbase {

public:
int funcion(void) { return 1; }
};
class Cderivada : public Cbase {
public:
int funcion(void) { return 2; }
};
int main(void) {
// =================
Cbase clasebase;
Cderivada clasederivada;
cout << "Base: " << clasebase.funcion() << endl;
cout << "Derivada: " << clasederivada.funcion() << endl;
cout << "Derivada-bis: " << clasederivada.Cbase::funcion() << endl; //
M.5
}
Salida:
Base: 1
Derivada: 2
Derivada-bis: 1

En este caso, la clase derivada redefine el mtodo funcion heredado de su ancestro, con lo que
en Cderivada existen dos versiones de esta funcin; una es privativa, la otra es heredada ( ).
Cuando se solicita al objeto clasederivada que ejecute el mtodo funcion, invoca a la versin
privativa; entonces se dice que la versin privativa oculta ("override") a la heredada. Para que sea
invocada la versin heredada (tercera salida), es preciso indicarlo expresamente. Esto se realiza
en la ltima lnea de main (M.5). La tcnica se denomina sobrecontrol de mbito y es explicada
con ms detenimiento en el siguiente epgrafe.
Sin embargo, la conclusin ms importante a resaltar aqu, es que las dos versiones
de funcion existentes en la clase derivada, no representan un caso de sobrecarga (no se dan las
condiciones exigidas
4.4.1a y ) ni de polimorfismo, ya que la funcin no ha sido declarada
como virtual ( 4.11.8a) en la superclase. Se trata sencillamente que ambas funciones
pertenecen a subespacios distintos dentro de la subclase.
Nota: la afirmacin anterior puede extraar al lector, ya que en captulos anteriores (
4.1.11c1) hemos sealado que no es posible definir subespacios dentro de las clases. Por
supuesto la afirmacin es cierta en lo que respecta al usuario (programador), pero no para el
compilador, que realiza automticamente esta divisin para poder distinguir ambas clases de
miembros (privativos y heredados).

En el captulo dedicado al acceso a subespacios en clases, hemos visto un mtodo


(declaracin using), por el que puede evitarse la ocultacin de la versin heredada, lo que sita a
ambas versiones (privativas y heredadas) en idntica situacin de visibilidad, y conduce a una
situacin de sobrecarga (
4.1.11c1)

6 Sobrecontrol de mbito

Hemos sealado que cuando se redefine un miembro que ya existe la clase base, el original queda
oculto para los miembros de la clase derivada y para sus posibles descendientes. Pero esto no
significa que el miembro heredado no exista en su forma original en la clase derivada [3]. Est
oculto por el nuevo, pero en caso necesario puede ser accedido sobrecontrolando el mbito como
se muestra en el siguiente ejemplo.
#include <iostream.h>
class B { public: int x; };
class D : public B {
public: char x;
};

// B clase raz
// D clase derivada
// redefinimos x

int main (void) {


// ========================
B b0;
// instancia de B
b0.x = 10;
// Ok: x pblico (int)
cout << "b0.x == " << b0.x << endl;
D d1;
// instancia de D
d1.x = 'c';
// Ok: x pblico (char)
d1.B::x = 13;
// sobrecontrolamos el mbito !!
cout << "Valores actuales: " << endl;
cout << " b0.x == " << b0.x << endl;
cout << " d1.x == " << d1.x << endl;
cout << " d1.x == " << d1.B::x << " (oculto)" << endl; // M.10
}
Salida:
b0.x == 10
Valores actuales:
b0.x == 10
d1.x == c
d1.x == 13 (oculto)
Comentario
Merece especial atencin observar que la notacin d1.B::x (M.10) representa al
elemento x en d1, tal como es heredado de la case antecesoraB. Esta variacin sintctica, permite
en algunos casos referenciar miembros que de otra forma permaneceran ocultos.
Resulta que los miembros de la clase derivada del ejemplo se reparten en dos mbitos de
nombres, el primero D (inmediatamente accesible), contiene los miembros privativos de la clase
derivada, por ejemplo d1.x. El segundo B, que contiene los miembros heredados de la
superclase, es accesible solo mediante el operador de acceso a mbito :: ( 4.9.19). Por
ejemplo: d1.B::x. En el captulo dedicado a las clases-base virtuales ( 4.11.2c1) se muestra
otro ejemplo muy ilustrativo.
Una representacin grfica idealizada de la situacin para la clase derivada D sera la siguiente:
namespace D {
char x
namespace B {
int x

}
}
Cuando desde el exterior se utiliza un identificador como d1.x; los nombres exteriores ocultan los
posibles identificadores interiores de igual nombre. Es decir: char x oculta a int x.

6.1 Cuando el sobrecontrol NO es necesario


Tenga muy en cuenta que las indicaciones anteriores relativas a ocultacin y sobrecontrol se
refieren exclusivamente al caso en que laredefinicin de una propiedad o mtodo de la
superclase, motive la existencia de una versin privativa de la subclase con el mismo nombre que
otra de la superclase. En caso contrario, los miembros heredados (pblicos) son libremente
accesibles sin necesidad de ningn mecanismo de acceso a mbito.
Considere la siguiente variacin del ejemplo anterior:
#include <iostream.h>
class B { public: int x; };
class D : public B {
public: int y;
};

// B clase raz
// D clase derivada
// exclusiva (no existe en B)

int main (void) {


// ========================
B b0;
// instancia de B
b0.x = 10;
// Ok: x pblico (int)
cout << "b0.x == " << b0.x << endl;
D d1;
// instancia de D
d1.x = 2;
// No precisa sobrecontrol!!
d1.y = 20;
// Ok: y pblico (int)
cout << "Valores actuales: " << endl;
cout << " b0.x == " << b0.x << endl;
cout << " d1.x == " << d1.x << " (NO oculto)" << endl;
cout << " d1.y == " << d1.y << endl;
}
Salida:
b0.x == 10
Valores actuales:
b0.x == 10
d1.x == 2 (NO oculto)
d1.y == 20

6.2 La misma tcnica de sobrecontrol de mbito anteriormente descrita


, puede utilizarse para
acceder a los miembros heredados de los antepasados ms lejanos de una clase. Utilizando un
smil biolgico, podramos decir que la totalidad de la carga gentica de los ancestros est
contenida en la instancia de cualquier clase que sea resultado de una derivacin mltiple. Lo
pondremos de manifiesto extendiendo el ejemplo anterior con una nueva derivacin de la
clase D que es ahora base de E, siendo B el ancestro ms remoto de E.
Ejemplo

#include <iostream.h>
class B { public: int x; };
class D : public B { public: char x; };
class E : public D { public: float x; };
int main (void) {
E e1;
e1.x = 3.14;
e1.D::x = 'd';
e1.B::x = 15;
cout << "Valores en
cout << " e1.x == "
cout << " e1.x == "
cout << " e1.x == "
}

// clase raz
// clase derivada
// clase derivada

// ========================
// instancia de E
// Ok: x pblico (foat)
// Ok: x pblico (char)
// Ok: x pblico (int)
e1: " << endl;
<< e1.x << endl;
<< e1.D::x << " (oculto)" << endl;
<< e1.B::x << " (oculto)" << endl;

Salida:
Valores en e1:
e1.x == 3.14
e1.x == d (oculto)
e1.x == 15 (oculto)

Temas relacionados:

Acceso a subespacios en clases (


4.1.11c1)
El mbito de nombres y la sobrecarga de funciones (
4.4.1a)
Acceso a travs de punteros en jerarquas de clases ( 4.11.2b1).
En este apartado
se comprueba como el sobrecontrol de mbito es innecesario (est implcito) en algunos
casos de acceso mediante punteros.

4.11.2b1 Punteros en jerarquas de clases


1 Sinopsis
Consideremos el caso de la clase general Polgono del epgrafe anterior (
la subclase de los tringulos
tendramos una jerarqua de
clases como en la figura:
Es evidente que todos los
issceles son tringulos y que
todos los tringulos son
polgonos. Esto significa que un
puntero a tipo polgono
(Poligono*) puede sealar a un
objeto tringulo (1) y a un objeto
issceles (2). Del mismo modo, un
puntero a tringulo (Triangulo*)
puede sealar a un objeto

4.11.2b). Expandiendo

issceles (3). Es decir:


Poligono Po;
Poligono* ptrP = &Po;
Triangulo PoTr;
Triangulo* ptrT = &PoTr;
Isosceles PoTrIs;
Isosceles* ptrI = &PoTrIs;
pero tambin:
ptrT = &PoTrIs;
ptrP = &PoTrIs;
ptrP = &PoTr;

// 3 correcto todo Issceles es un tringulo


// 2 correcto todo Issceles es un polgono
// 1 correcto todo tringulo es un polgono

Observe que en los tres casos, se estn utilizando punteros a una superclase para designar
objetos de la clase derivada. O dicho de otro modo: la direccin de un objeto de la subclase se
expresa mediante un puntero de tipo puntero-a-superclase. Esta posibilidad, conocida como
"upcast", es tremendamente til en determinadas circunstancias [3].

Tenga en cuenta que el razonamiento inverso no es necesariamente cierto. Es decir, un polgono


no es necesariamente tringulo y un tringulo no es necesariamente issceles; en otras palabras:
un puntero a issceles no puede utilizarse indiscriminadamente para sealar a un tringulo ni a un
polgono:
ptrI = &PoTr;
ptrI = &Po;
ptrT = &Po;

// Error: un tringulo puede no ser issceles


// Error: un polgono puede no ser issceles
// Error: un polgono puede no ser un tringulo

2 Teorema
Estas consideraciones pueden generalizarse en el siguiente enunciado: si una subclase S tiene
una clase-base pblica B, entonces S* puede ser asignada a una variable de tipo B* sin ninguna
conversin explcita de tipo (esto es lo que expresan las sentencias 3 y 1 anteriores). Lo inverso no
es cierto; en estos casos se hace necesaria una conversin explcita de tipo (
4.9.9).
Lo anterior puede resumirse en los siguientes axiomas (que son equivalentes):

Los objetos de las clases derivadas pueden tratarse como si fuesen objetos de sus clasesbase cuando se manipulan mediante punteros y referencias.
Un puntero de una clase-base puede contener direcciones de objetos de cualquiera de sus
clases derivadas. Ejemplo [1]:

class B { .... };
class D : public B { ... };
...
func () {
B* bptr = new D; // puntero a superclase asignado a objeto de subclase
...
delete bptr;
}

Nota: el hecho de que el puntero a una superclase pueda ser utilizado como puntero a objeto
de cualquier subclase de su jerarqua (una especie de "puntero genrico" para los objetos de
la familia), se cumple tambin en otros lenguajes como Eiffel o Java. Como estos lenguajes
proporcionan una superclase de la que derivan todas las dems (en Java es la clase Object),
resulta que un puntero a esta superclase Java equivale funcionalmente al papel del
puntero void* en C++ (
4.2.1d).

Observar el lector que los razonamientos anteriores 1


: "Todo issceles es un tringulo" y "un
tringulo puede no ser issceles", son desde luego vlidos en geometra, pero no necesariamente
en las jerarquas de clases. Sobre todo el primero de tales razonamientos podra no ser cierto en
algn caso concreto, ya que el programador es totalmente libre para definirla. Sera posible disear
una jerarqua "enloquecida" [4], en la que no se cumpliesen estas premisas lgicas. Como en
cualquier caso el compilador garantizar la validez del teorema anterior, la congruencia y la lgica
aconsejan que en el diseo de jerarquas de clases se cumpla el denominado principio de
sustitucin de Liskov o LSP ("Liskov Substitution Principle"), segn el cual las clases se deben
disear de forma que cualquier clase derivada sea aceptable donde lo sea su superclase.

3 Acceso a travs de punteros


Cuando se tienen objetos de clases aisladas (que no derivan de ninguna otra), el acceso mediante
punteros a dichas clases, no presenta dificultad alguna (
4.2.1f), basta utilizar el selector
indirecto -> ( 4.9.16). Ejemplo:
class C { public: int x; }
...
C c;
// Objeto (instancia) de la clase
C* ptr = &c;
// puntero a clase
ptr->x = 10;
// Ok. acceso a miembro del objeto: c.x == 10

Sin embargo, cuando las clases pertenecen a una jerarqua, su acceso mediante punteros puede
convertirse en una pesadilla si no se conoce ntimamente como se comportan frente a los espacios
de nombres implcitos en tales clases (
4.11.2b). Considere detenidamente el siguienteejemplo:
#include <iostream.h>
class B {
// Superclase (raz)
public: int f(int i) { cout << "Funcion-Superclase "; return i; }
};
class D : public B {
// Subclase (derivada)
public: int f(int i) { cout << "Funcion-Subclase "; return i+1; }
};
int main() {
D d;
D* dptr = &d;
B* bptr = dptr;
subclase
cout << "d.f(1)
";
cout << d.f(1) << endl;
pblico)

//
//
//
//

==========
instancia de subclase
puntero-a-subclase sealando objeto
puntero-a-superclase sealando objeto de

// acceso directo al mtodo (que es

cout
cout
cout
cout

<<
<<
<<
<<

"dptr->f(1) ";
dptr->f(1) << endl;
"bptr->f(1) ";
bptr->f(1) << endl;

// acceso a travs del puntero


// idem.

}
Salida:
d.f(1)
Funcion-Subclase 2
dptr->f(1) Funcion-Subclase 2
bptr->f(1) Funcion-Superclase 1
Comentario
Vemos que en la primera y segunda salida las cosas ocurren como de costumbre, ya sea utilizando
el operador de acceso directo o el indirecto (mediante puntero). En ambos casos, la nueva
definicin de f en la clase derivada, oculta la definicin en la superclase. La sorpresa ocurre en la
tercera salida, donde a pesar de que el puntero seala al mismo (y nico objeto) d, se accede
directamente al subespacio B del objeto, con lo que la versin f utilizada es la existente en el
mismo; la heredada de la superclase [2].
Este ltimo resultado podra obtenerse tambin mediante:
cout << d.B::f(1) << endl;
As pues, como corolario de lo anterior, tenga en cuenta que (en condiciones normales *), el
acceso a objetos d de subclases D mediante punteros a superclases B*, lleva implcita la
referencia el subespacio B existente en el objeto d.
* Al tratar de las funciones virtuales (
4.11.8a), veremos que la "sorpresa" anterior puede
evitarse con una pequesima modificacin en la definicin del mtodo f de la superclase
(declarndolo virtual).

4.11.2b2 El mbito de nombres y la sobrecarga de mtodos


1 Sinopsis
Se ha indicado ( 4.4.1a) que en C++ no existe el concepto de sobrecarga a travs de mbitos de
nombres, y los mbitos de las clases derivadas no son una excepcin a esta regla general. Esto
significa que el mecanismo de sobrecarga no funciona para las clases derivadas.
Lo pondremos de manifiesto con un sencillo ejemplo modificando ligeramente el anterior (
4.11.2b1), de forma que las versiones del mtodo f en la clase-base B y en la derivada D,
pudieran ser objeto de sobrecarga:
#include <iostream.h>
class B {
// Superclase
public: int f(int i) { cout << "Funcion-Superclase "; return i; }
};

class D : public B {
// Subclase
public: float f(float f) { cout << "Funcion-Subclase "; return f+0.1; }
};
int main() {
D d;
D* dptr = &d;
B* bptr = dptr;
subclase
cout << "d.f(1)
cout << "d.f(1.1)

//
//
//
//

=================
instancia de subclase
puntero-a-subclase sealando objeto
puntero-a-superclase sealando objeto de

" << d.f(1) << endl;


" << d.f(1.1) << endl;

cout << "dptr->f(1)


" << dptr->f(1) << endl;
cout << "dptr->f(1.1) " << dptr->f(1.1) << endl;
cout << "bptr->f(1)
" << bptr->f(1) << endl;
cout << "bptr->f(1.1) " << bptr->f(1.1) << endl;
}
Salida:
d.f(1)
d.f(1.1)
dptr->f(1)
dptr->f(1.1)
bptr->f(1)
bptr->f(1.1)

Funcion-Subclase 1.1
Funcion-Subclase 1.2
Funcion-Subclase 1.1
Funcion-Subclase 1.2
Funcion-Superclase 1
Funcion-Superclase 1

Comentario
Comprobamos que cualquiera que sea la forma de invocacin utilizada, en ningn caso se produce
sobrecarga de la funcin; siempre se accede a la misma versin, dependiendo del subespacio de
nombres B o D referenciado.
Nota: si el mecanismo de sobrecarga de funciones hubiese funcionado entre los subespacios de
nombres del objeto d, las salidas habran sido:
d.f(1)
d.f(1.1)
dptr->f(1)
dptr->f(1.1)
bptr->f(1)
bptr->f(1.1)

Funcion-Superclase 1
Funcion-Subclase 1.2
Funcion-Superclase 1
Funcion-Subclase 1.2
Funcion-Superclase 1
Funcion-Subclase 1.2

En estas condiciones cabe preguntarse Que podramos hacer si realmente necesitamos el


funcionamiento del mecanismo de sobrecarga?. Es decir, si deseamos que, en concordancia con
los argumentos de llamada, sea invocada la versin de f definida en la subclase o de la
superclase.
La solucin est en utilizar la declaracin using (
4.1.11c). Vemoslo mediante un ejemplo
modificando ligeramente el caso anterior (para simplificar el cdigo se han disminuido las salidas).

#include <iostream.h>
class B {
// Superclase
public: int f(int i) { cout << "Funcion-Superclase "; return i; }
};
class D : public B {
// Subclase
public:
using B::f;
// Ok. acceder a las versiones de f en B
float f(float f) { cout << "Funcion-Subclase "; return f+0.1; }
};
int main() {
D d;
D* dptr = &d;
B* bptr = dptr;
cout
cout
cout
cout

<<
<<
<<
<<

// =================

dptr->f(1) << endl;


dptr->f(1.0) << endl;
bptr->f(1) << endl;
bptr->f(1.0) << endl;

}
Salida [1]:
Funcion-Superclase 1
Funcion-Subclase 1.1
Funcion-Superclase 1
Funcion-Superclase 1
Comentario
En este caso comprobamos como la sobrecarga ha funcionado en el sentido D
B. Cuando
accedemos al espacio de nombres D, se invoca correctamente la versin adecuada de f, pero
cuando accedemos a B, el espacio D sigue siendo invisible para el mecanismo de sobrecarga.
La representacin grfica idealizada de la situacin para la clase derivada D sera la siguiente:
namespace D {
using B::f;
float f(float f);
namespace B {
int f(inf i);
}
}
Cuando se utiliza un identificador como d.f(), o su equivalente dptr->f(), se accede
directamente al subespacio D, aunque las versiones interiores de f son accesibles por la
declaracin using; pero cuando se accede directamente al subespacio B mediante expresiones
como bptr->f(), la versin de f en este subespacio sigue ocultando cualquier otro identificador
exterior.
Ver otro ejemplo en: "Acceso a subespacios en clases" (

4.1.11c1)

Inicio.

4.11.2c Declaracin por herencia mltiple


1 Sinopsis
La tercera forma de crear una nueva clase es por herencia mltiple, tambin
llamada agregacin o composicin [1]. Consiste en el ensamblando una nueva clase con los
elementos de varias clases-base. C++ permite crear clases derivadas que heredan los miembros
de una o ms clases antecesoras. Es clsico sealar el ejemplo de un coche, que tiene un motor;
cuatro ruedas; cuatro amortiguadores, etc. Elementos estos pertenecientes a la clase de los
motores, de las ruedas, los amortiguadores, etc.
Como en el caso de la herencia simple, aparte de los miembros heredados de cada clase
antecesora, la nueva clase tambin puede tener miembros privativos (
4.11.2b)

2 Sintaxis
Cuando se declara una clase D derivada de varias clases base: B1, B2, ... se utiliza una lista de
las bases directas (
4.11.2b) separadas por comas. La sintaxis general es:
class-key <info> nomb-clase <: lista-base> { <lista-miembros> };
El significado de cada miembro se indic al tratar de la declaracin de una clase (
este caso, la declaracin de D seria:

4.11.2). En

class-key <info> D : <B1, B2, ...> { <lista-miembros> };

D hereda todos los miembros de las clases antecesoras B1, B2, etc, y solo puede utilizar los
miembros que derivan de pblicos y protegidos en dichas clases. Resulta as que un objeto de la
clase derivada contiene sub-objetos de cada una de las clases antecesoras.

2.1 Restricciones

Tenga en cuenta que las clases antecesoras no pueden


repetirse, es decir:
class B { ... };
class D : B, B, ... { ... };

// Ilegal!

Aunque la clase antecesora no puede ser base directa ms


que una vez, si puede repetirse como base indirecta. Es la
situacin recogida en el siguiente ejemplo cuyo esquema se
muestra en la figura 1:
class
class
class
class

B { ... };
C1 : public B { ... };
C2 : public B { ... };
D : public C1, C2 { ... };

Aqu la clase D tiene miembros heredados de sus


antecesoras D1 y D2, y por consiguiente, dos sub-objetos
de la base indirecta B.

Fig. 1
El mecanismo sucintamente descrito, constituye lo que se
denomina herencia mltiple ordinaria (o simplemente
herencia). Como se ha visto, tiene el inconveniente de que
si las clases antecesoras contienen elementos comunes,
estos se ven duplicados en los objetos de la subclase. Para
evitar estos problemas, existe una variante de la misma,
la herencia virtual (
4.11.2c1), en la que cada objeto de
la clase derivada no contiene todos los objetos de las
clases-base si estos estn duplicados.

Fig. 2

Las dependencias derivadas de la herencia mltiple suele


ser expresada tambin mediante un grafo denominado DAG ("Direct acyclic graph"), que tiene la
ventaja de mostrar claramente las dependencias en casos de composiciones complicadas. La
figura 2 muestra el DAG correspondiente al ejemplo anterior. En estos grafos las flechas indican el
sentido de la herencia, de forma que A --> B indica que A deriva directamente de B. En nuestro
caso se muestra como la clase D contiene dos sub-objetos de la superclase B.
Nota: la herencia mltiple es uno de los puntos peliagudos del lenguaje C++ (y de otros que
tambin implementan este tipo de herencia). Hasta el extremo que algunos tericos
consideran que esta caracterstica debe evitarse, ya que adems de las tericas, presenta
tambin una gran dificultad tcnica para su implementacin en los compiladores. Por ejemplo,
surge la cuestin: si dos clases A y B conforman la composicin de una subclase D, y ambas
tienen propiedades con el mismo nombre, Que debe resultar en la subclase D? Miembros
duplicados, o un miembro que sean la agregacin de las propiedades de A y B?. Como
veremos a continuacin, el creador del C++ opt por un diseo que despeja cualquier posible
ambigedad, aunque ciertamente deriva en una serie de reglas y condiciones bastante
intrincadas.

3 Ambigedades

La herencia mltiple puede originar situaciones de ambigedad cuando una subclase contiene
versiones duplicadas de sub-objetos de clases antecesoras o cuando clases antecesoras
contienen miembros del mismo nombre:
class B {
public:
int b;
int b0;
};
class C1 : public B {
public:
int b;
int c;
};
class C2 : public B {
public:
int b;
int c;
};
class D: public C1, C2 {
public:
D() {
c = 10;
// L1: Error
C1::c = 110;
// L2: Ok.
C2::c = 120;
// L3: Ok.
b = 12; Error!!
// L4: Error
C1::b = 11;
// L5: Ok.
C2::b = 12;
// L6: Ok.
C1::B::b = 10;
// L7: Error
B::b = 10;
// L8: Error
B
b0 = 0;
// L9: Error
C1::b0 = 1;
// L10: Ok.
C2::b0 = 2;
// L11: Ok.

ambigedad C1::c o C2::c ?


ambigedad
C1::b domina sobre C1::B::b
C2::b domina sobre C2::B::b
de sintaxis!
ambigedad. No existe una nica base
ambigedad

}
};

Los errores originados en el constructor de la clase D son


muy ilustrativos sobre los tipos de ambigedad que puede
originar la herencia mltiple (podran haberse presentado
en cualquier otro mtodo D::f() de dicha clase).
En principio, a la vista de la figura 1 , podra parecer que
las ambigedades relativas a los miembros de D deberan
resolverse mediante los correspondientes especificadores
de mbito:
C1::B::m
B
C2::B::m
B
C1::n
C2::n

// miembros m en C1 heredados de
// miembros m en C2 heredados de
// miembros n en C1 privativos
// miembros n en C2 privativos

Como puede verse en la sentencia L7


, por desgracia el
asunto no es exactamente as (otra de las inconsistencias
del lenguaje). El motivo es que el esquema mostrado en la
figura es mramente conceptual, y no tiene que
corresponder necesariamente con la estructura de los
objetos creados por el compilador. En realidad un objeto
suele ser una regin continua de memoria. Los objetos de
las clases derivadas se organizan concatenando los subFig. 3
objetos de las bases directas, y los miembros privativos si
los hubiere; pero el orden de los elementos de su interior no est garantizado (depende de la
implementacin). La figura 3 muestra una posible organizacin de los miembros en el interior de los
objetos del ejemplo.
El crador del lenguaje indica al respecto [2] que las relaciones contenidas en un grafo como el de la
figura 2
representan informacin para el programador y para el compilador, pero que esta
informacin no existe en el cdigo final. El punto importante aqu es entender que la organizacin
interna de los objetos obtenidos por herencia mltiple es idntico al de los obtenidos por herencia
simple. El compilador conoce la situacin de cada miembro del objeto en base a su posicin, y
genera el cdigo correspondiente sin indirecciones u otros mecanismos innecesarios (disposicin
del objeto D en la figura 3).

3.1 Desde el punto de vista de la sintaxis de acceso, cualquier miembro m privativo de D (zona5) de un objeto d puede ser referenciado comod.m. Cualquier otro miembro del mismo nombre (m)
en alguno de los subobjetos queda eclipsado por este. Se dice que este identificador domina a los
dems [3].

Nota: este principio de dominancia funciona tambin en los subobjetos C1 y C2. Por ejemplo:
si un identificador n en el subobjeto C1 est duplicado en la parte privativa de C1 y en la parte
heredada de B, C1::n tienen preferencia sobre C1::B::n.

Cualquier objeto c privativo de los subobjetos C1 o C2 (zonas 2 y 4) podra ser accedido


como d.c. Pero en este caso existe ambigedad sobre cual de las zonas se utilizar. Para
resolverla se utiliza el especificador de mbito: C1::c o C2::c. Este es justamente el caso de las
sentencias L1/L3 del ejemplo:
c = 10;
C1::c = 110;
C2::c = 120;

// L1: Error ambigedad C1::c o C2::c ?


// L2: Ok.
// L3: Ok.

Es tambin el caso de las sentencias L4/L6. Observe que en este caso no existe ambigedad
respecto a los identificadores b heredados (zonas 1, y 2) porque los de las zonas 2 y 4 tienen
preferencia sobre los de las zonas 1 y 2.
b = 12; Error!!
C1::b = 11;
C2::b = 12;

// L4: Error ambigedad


// L5: Ok.
C1::b domina sobre C1::B::b
// L6: Ok.
C2::b domina sobre C2::B::b

Es interesante sealar que estos ltimos, los identificadores b de las zonas 1 y 2 (heredados de
B) no son accesibles porque siempre quedan ocultos por los miembros dominantes, y la gramtica
C++ no ofrece ninguna forma que permita hacerlo en la disposicin actual del ejemplo. Son los
intentos fallidos sealados en L7 y L8:
C1::B::b = 10;
B::b = 10;

// L7: Error de sintaxis!


// L8: Error ambigedad. No existe una unica base B

El error de L8 se refiere a que existen dos posibles candidatos (zonas 1 y 2). Al tratar de la
herencia virtual (
4.11.2c1) veremos un mtodo de resolver (parcialmente) este problema.
Cuando no existe dominancia, los identificadores b0 de las zonas 1 y 2 si son visibles, aunque la
designacin directa no es posible porque existe ambigedad sobre la zona 1-2 a emplear. Es el
caso de las sentencias L9/L11:
b0 = 0;
C1::b0 = 1;
C2::b0 = 2;

// L9: Error ambigedad


// L10: Ok.
// L11: Ok.

4 Modificadores de acceso
Los modificadores de acceso en la lista-base pueden ser cualquiera de los sealados al
referirnos a la herencia simple (public, protected yprivate
4.11.2b-1), y pueden ser distintos
para cada uno de los ancestros. Ejemplo:
class D : public B1, private B2, ... { <lista-miembros> };

struct T : private D, E { <lista-miembros> };


// Por defecto E equivale a 'public E'

4.11.2c1 Clases-base virtuales

1 Sinopsis
Hemos sealado que en herencia mltiple, las clases
antecesoras no pueden repetirse:
class B { .... };
class D : B, B, ... { ... };

// Ilegal!

aunque si pueden repetirse indirectamente:


class
class
class
class
Ok.

B { .... };
C1 : public B { ... };
C2 : public B { ... };
D : public C1, public C2 { ... };

//

En este caso, cada objeto de la clase D tiene dos


subobjetos de la clase B. Es la situacin mostrada en el
ejemplo de la pgina anterior ( 4.11.2c).
Fig. 1
Si esta duplicidad puede causar problemas, o sencillamente
no se desea, puede aadirse la palabra virtual a la
declaracin de las clases-base, con lo que B es ahora
una clase-base virtual y D solo contiene un subobjeto de
dicha clase:
class
class
class
class

B { .... };
C1 : virtual public B { ... };
C2 : virtual public B { ... };
D : public C1, public C2 { ... };

Fig. 2
// Ok.

La nueva situacin se muestra en la figura 1 y en el DAG de la figura 2.

2 virtual (palabra-clave)
virtual es una palabra-clave C++ que tiene dos acepciones completamente diferentes
dependiendo del contexto de su utilizacin. Utilizada con nombres de clase sirve para controlar
aspectos del mecanismo de herencia; utilizada con nombres de funciones-miembro, controla
aspectos del polimorfismo y del tipo de enlazado que se utiliza para tales funciones [1].

2.1 Sintaxis

La sintaxis de la palabra-clave virtual admite dos variantes que reflejan la dualidad de su


utilizacin:
virtual <nombre-de-clase>
virtual <nombre-de-funcion-miembro>

La primera forma sintctica, cuando se antepone al nombre de una clase-base, como en el caso
anterior, declara una clase-base virtual, que da lugar a un mecanismo denominado herencia
virtual (en contraposicin con la herencia ordinaria), cuya descripcin abordamos en este epgrafe.
Ejemplo:
class D : virtual B { /* ... */ };
es equivalente a:
class D : private virtual B { /* ... */ };
Tenga en cuenta que en estos casos, el calificador virtual solo afecta al identificador que le sigue
inmediatamente. Ejemplo:
class E : public virtual B, C, D { /* ... */ };
En este caso las clases B, C y D son bases pblicas directas, pero solo B es virtual.

La segunda forma, cuando se aplica a la funcin miembro de una clase-base, define dicho
mtodo como funcin virtual, lo que permite que las clases derivadas puedan definir diferentes
versiones de la misma funcin-base (virtual) an en el caso de que coincidan los argumentos (no
se trate por tanto de un caso de sobrecarga), y les proporciona un mtodo especial de enlazado
(Enlazado Retrasado
1.4.4).
Las funciones virtuales redefinidas en las clases derivadas, solapan u ocultan a la versin
definida en la superclase, dndole a las clases a que pertenecen el carcter de polimrficas (
4.11.8). Todos los aspectos de este segundo uso, se detalla en el apartado correspondiente a
las Funciones virtuales (
4.11.8a). Las clases-base virtuales son clases perfectamente normales,
y nada impide que puedan definirse en ellas funciones virtuales.

3 Herencia virtual
Hemos sealado, que en una herencia mltiple ordinaria, en la que indirectamente se repite una
superclase (como en el ejemplo inicial
), los objetos de la clase derivada contienen mltiples
subobjetos de la superclase. Esta duplicidad puede evitarse declarando virtuales las superclases.
Como ejemplo de aplicacin se incluye un caso anlogo al estudiado en el captulo anterior (
4.11.2c) en el que se utiliza herencia virtual (en esta ocasin utilizamos una versin ejecutable
para comprobar las salidas).
#include <iostream>
using namespace std;
class B {

public:
int b;
int b0;
};
class C1 : public virtual B {
public:
int b;
int c;
};
class C2 : public virtual B {
public:
int b;
int c;
};
class D: public C1, C2 {
public:
D() {
c = 10;
// L1: Error
C1::c = 110;
// L2: Ok.
C2::c = 120;
// L3: Ok.
b = 12; Error!!
// L4: Error
C1::b = 11;
// L5: Ok.
C2::b = 12;
// L6: Ok.
C1::B::b = 10;
// L7: Error
B::b = 10;
// L8: Ok.!!
b0 = 0;
// L9: Ok.!!
C1::b0 = 1;
// L10: Ok.
C2::b0 = 1;
// L11: Ok.
}
};

ambigedad C1::c o C2::c ?


ambigedad
C1::b domina sobre C1::B::b
C2::b domina sobre C2::B::b
de sintaxis!

int main() {
// ================
D d;
cout << "miembro b0: " << d.C1::b0 << endl; // M2:
++d.C2::b0;
cout << "miembro b0: " << d.C1::b0 << endl; // M4:
return 0;
}

Salida:
miembro b0: 1
miembro b0: 2
Comentario:
La figura 3 muestra la posible disposicin de los miembros
en los objetos de las clases utilizadas. Comprese con la
correspondiente figura del captulo anterior cuando la
herencia no es virtual (
Figura). El comportamiento y las
medidas adoptadas para evitar los errores son los ya
descritos. Con la diferencia de que aqu han desaparecido
los errores de las sentencias L8 y L9. La razn es la misma
en ambos casos: como se indica en la figura, los objetos de
la clase D solo tienen un subobjeto heredado de la
superclase B, por lo que no hay ambigedad al respecto.
Es significativo que a pesar de ser posible la asignacin
directa de L9. An son posibles las sentencias L10 y L11.
En realidad, las tres ltimas sentencias inicializan al mismo
miembro. Esto puede comprobarse en las sentencias de
salida M2 y M4, en las que se incrementa el
miembro b0 como subobjeto de C2 y se interroga como
subobjeto de C1.
3.1 Para mayor abundamiento, se aade otro ejemplo
que muestra como, en una herencia mltiple ordinaria, en Fig. 3
la que indirectamente se repite una superclase, los objetos
de la clase derivada contienen mltiples subobjetos de la superclase. Su existencia se pone de
manifiesto sobrecontrolando adecuadamente los identificadores (
4.11.2b):
#include <iostream.h>
class B {
//
public: int pub;
B() { pub = 0; }
//
};
class X : public B {};
//
class Y : public B {};
//
class Z : public X, public Y
int main ()
Z z1;
cout <<
cout <<
cout <<
z1.X::pub
z1.Y::pub
cout <<
cout <<
cout <<
}

{
"Valores
" z1.pub
" z1.pub
= 1;
= 2;
"Valores
" z1.pub
" z1.pub

clase raz
constructor por defecto
L.7
L.8
{};

// =====================
// instancia de Z
iniciales:" << endl;
(de X) == " << z1.X::pub << endl;
(de Y) == " << z1.Y::pub << endl;
// sobrecontrolamos el identificador
// L.17
finales:" << endl;
(de X) == " << z1.X::pub << endl;
(de Y) == " << z1.Y::pub << endl;

Salida:
Valores
z1.pub
z1.pub
Valores
z1.pub
z1.pub

iniciales:
(de X) == 0
(de Y) == 0
finales:
(de X) == 1
(de Y) == 2

Cuando no sea deseable esta repeticin de subobjetos, la herencia virtual evita la multiplicidad.
Observe el resultado del ejemplo anterior declarando las clases X e Y como clases-base
virtuales. El programa sera exactamente igual, excepto el cambio de las lneas 7 y 8 por las
siguientes:
class X : virtual public B {};
class Y : virtual public B {};

// L.7 bis
// L.8 bis

Salida:
Valores iniciales:
z1.pub (de X) ==
z1.pub (de Y) ==
Valores finales:
z1.pub (de X) ==
z1.pub (de Y) ==

0
0
2
2

En este caso, los identificadores z1.X::pub y z1.Y::pub sealan al mismo elemento, rezn por
la que las dos ltimas salidas corresponden a la ltima asignacin realizada (lnea 17).

3.1 Constructores de clases-base virtuales


Los constructores de las superclases virtuales son invocados antes que los de cualquier otra
superclase no-virtual.
Si la jerarqua de clases contiene mltiples clases-base virtuales, sus respectivos constructores son
invocados en el mismo orden que fueron declaradas. Despus se invocan los constructores de las
clases-base no virtuales, y por ltimo el constructor de la clase derivada.
No obstante lo anterior, si una clase virtual deriva de una superclase no-virtual, el constructor de
esta es invocado primero, a fin de que la clase virtual derivada pueda ser construida
adecuadamente. Por ejemplo las sentencias:
class X : public Y, virtual public Z {};
X clasex;
originan el siguiente orden de invocacin de constructores:
Z();
Y();
X();

// inicializacin de la clase-base virtual


// inicializacin de la clase-base no-virtual
// inicializacin de la clase derivada

4.11.2d Construccin y destruccin de objetos

1022

You might also like