13.01.2015 Views

Pensar en C++ (Volumen 1) - Grupo ARCO

Pensar en C++ (Volumen 1) - Grupo ARCO

Pensar en C++ (Volumen 1) - Grupo ARCO

SHOW MORE
SHOW LESS

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 />

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!