Pensar en C++ (Volumen 1) - Grupo ARCO
Pensar en C++ (Volumen 1) - Grupo ARCO
Pensar en C++ (Volumen 1) - Grupo ARCO
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
✐<br />
✐<br />
✐<br />
“Volum<strong>en</strong>1” — 2012/1/12 — 13:52 — page 444 — #482<br />
✐<br />
Capítulo 15. Polimorfismo y Funciones virtuales<br />
tringed, y Brass <strong>en</strong>cajan <strong>en</strong> esta categoría porque derivan de Instrum<strong>en</strong>t (esto<br />
hace que t<strong>en</strong>gan la misma interfaz de Instrum<strong>en</strong>t, y puedan responder a los mismos<br />
m<strong>en</strong>sajes), lo que implica que sus direcciones pued<strong>en</strong> ser metidas <strong>en</strong> el array. Sin<br />
embargo, el compilador no sabe que sean otra cosa que objetos de tipo Instrum<strong>en</strong>t,<br />
por lo que normalm<strong>en</strong>te llamará a las versiones de las funciones que estén <strong>en</strong> la<br />
clase base. Pero <strong>en</strong> este caso, todas las funciones han sido declaradas con la palabra<br />
reservada virtual, por lo que ocurre algo difer<strong>en</strong>te. Cada vez que se crea una clase<br />
que conti<strong>en</strong>e funciones virtuales, o se deriva de una clase que conti<strong>en</strong>e funciones virtuales,<br />
el compilador crea para cada clase una única VTABLE, que se puede ver a la<br />
derecha <strong>en</strong> el diagrama. En ésta tabla se colocan las direcciones de todas las funciones<br />
que son declaradas virtuales <strong>en</strong> la clase o <strong>en</strong> la clase base. Si no se sobreescribe<br />
una función que ha sido declarada como virtual, el compilador usa la dirección de la<br />
versión que se <strong>en</strong>cu<strong>en</strong>tra <strong>en</strong> la clase base (esto se puede ver <strong>en</strong> la <strong>en</strong>trada adjusta<br />
de la VTABLE de Brass). Además, se coloca el VPTR (descubierto <strong>en</strong> Sizes.cpp)<br />
<strong>en</strong> la clase. Hay un único VPTR por cada objeto cuando se usa her<strong>en</strong>cia simple como<br />
es el caso. El VPTR debe estar inicializado para que apunte a la dirección inicial de la<br />
VTABLE apropiada (esto sucede <strong>en</strong> el constructor que se verá más tarde con mayor<br />
detalle).<br />
Una vez que el VPTR ha sido inicializado a la VTABLE apropiada, el objeto "sabe"<br />
de que tipo es. Pero este autoconocimi<strong>en</strong>to no ti<strong>en</strong>e valor a m<strong>en</strong>os que sea usado <strong>en</strong><br />
el mom<strong>en</strong>to <strong>en</strong> que se llama a la función virtual.<br />
Cuando se llama a una función virtual a través de la clase base (la situación que se<br />
da cuando el compilador no ti<strong>en</strong>e toda la información necesaria para realizar la ligadura<br />
estática), ocurre algo especial. En vez de realizarse la típica llamada a función,<br />
que <strong>en</strong> l<strong>en</strong>guaje <strong>en</strong>samblador es simplem<strong>en</strong>te un CALL a una dirección <strong>en</strong> concreto,<br />
el compilador g<strong>en</strong>era código difer<strong>en</strong>te para ejecutar la llamada a la función. Aquí<br />
se muestra a lo que se parece una llamada a adjust() para un objeto Brass, si se<br />
hace a través de un puntero a Instrum<strong>en</strong>t (una refer<strong>en</strong>cia a Instrum<strong>en</strong>t produce<br />
el mismo efecto):<br />
puntero a<br />
Instrum<strong>en</strong>t<br />
objeto Brass<br />
vptr<br />
[0]<br />
[1]<br />
[2]<br />
VTABLE de Brass<br />
&Brass::play<br />
&Brass::what<br />
&Brass::adjust<br />
Figura 15.2: Tabla de punteros virtuales<br />
El compilador empieza con el puntero a Instrum<strong>en</strong>t, que apunta a la dirección<br />
inicial del objeto. Todos los objetos Instrum<strong>en</strong>t o los objetos derivados de Instrum<strong>en</strong>t<br />
ti<strong>en</strong><strong>en</strong> su VPTR <strong>en</strong> el mismo lugar (a m<strong>en</strong>udo al principio del objeto), de tal<br />
forma que el compilador puede conseguir el VPTR del objeto. El VPTR apunta a la<br />
la dirección inicial de VTABLE. Todas las direcciones de funciones de las VTABLE<br />
están dispuestas <strong>en</strong> el mismo ord<strong>en</strong>, a pesar del tipo específico del objeto. play() es<br />
el primero, what() es el segundo y adjust() es el tercero. El compilador sabe que<br />
a pesar del tipo específico del objeto, la función adjust() se <strong>en</strong>cu<strong>en</strong>tra localizada <strong>en</strong><br />
VPTR+2. Debido a esto, <strong>en</strong> vez de decir, "Llama a la función <strong>en</strong> la dirección absoluta<br />
Instrum<strong>en</strong>t::adjust() (ligadura estática y acción equivocada), se g<strong>en</strong>era código<br />
que dice "Llama a la función que se <strong>en</strong>cu<strong>en</strong>tre <strong>en</strong> VPTR+2". Como la búsqueda del<br />
VPTR y la determinación de la dirección de la función actual ocurre <strong>en</strong> tiempo de<br />
ejecución, se consigue la deseada ligadura dinámica. Se <strong>en</strong>vía un m<strong>en</strong>saje al objeto,<br />
444<br />
✐<br />
✐<br />
✐<br />
✐