Está en la página 1de 291

C++/OOP

UN ENFOQUE PRCTICO

RICARDO DEVIS BOTELLA

C++/OOP: UN ENFOQUE PRCTICO Pgina 1/297


A Consol

C++/OOP: UN ENFOQUE PRCTICO Pgina 2/297


INTRODUCCIN
1
Es costumbre que las primeras lneas de un texto procuren, de alguna
manera, al lector una suerte de explicacin del talante y nimo del autor al
escribir la obra que tiene ante s. Bien: he aqu un ensimo libro sobre C++
y -cmo no?- sobre Programacin Orientada a Objetos. De acuerdo,
pensar el lector, pero por qu ste y no otro?; o mejor, qu tiene de
especial el presente texto? Y la respuesta es... una intencin eminentemente
didctica! Lo que se pretende es introducir al lector en los esquemas bsicos
de la programacin orientada-a-objetos -que en adelante llamaremos OOP-
a travs del uso de un lenguaje de amplia aceptacin industrial, cual es
C++. La aproximacin ser, sobre todo, prctica: procurar no perderme en
la maraa de siglas y conceptos que pueblan esta metodologa y que
frecuentemente desaniman al principiante, de forma que, tras el inevitable
discurso terico, siempre se buscar la aplicacin concreta de lo expuesto
mediante cdigo en C++. Se trata, pues, de una introduccin al lenguaje
C++, pero, atencin, utilizando de forma inseparable las tcnicas y
conceptos de OOP. El texto tambin quiere ser, por fin, ameno y, en lo
posible, divertido: la tradicin norteamericana de obras en las que el rigor
no est reido con un cierto humor, en ocasiones salvaje, ser aqu
observada con cierta complacencia. El tono ser, pues, desenfadado pero
exacto: a veces elemental, a veces no tanto.

C++/OOP: UN ENFOQUE PRCTICO Pgina 3/297


A QUIN VA DIRIGIDO ESTE LIBRO?

No debemos engaarnos: las tcnicas de OOP (y por tanto de C++, al que


ya desde ahora deberemos acostumbrarnos a considerar como bien distinto
de C) son difciles de asimilar. Insisto: no slo nos encontramos ante un
rea compleja, sino prolija, con abundancia de estndares y sumida en un
continuo cambio evolutivo. Las experiencias en U.S.A. indican que los
estudiantes tardan de seis a nueve meses (si no ms) en asimilar
verdaderamente y poner en prctica de forma efectiva los conceptos,
tcnicas y metodologas aprendidas. As que debo suponer que el lector
tendr conocimientos de algn lenguaje estructurado, como Fortran, Pascal,
C, etc. Dado que pretendemos trabajar en C++ y que este lenguaje
comparte muchas de las bases de C, sera deseable que el lector conociera al
menos las bases del lenguaje C, aunque lo ideal sera tener alguna
experiencia en ANSI C. De cualquier forma existe una gran profusin de
textos didcticos sobre C y ANSI C, por lo que se obviarn las explicaciones
sobre las construcciones en tales lenguajes.

QU MATERIAL SE NECESITA?

Debo insistir en un tpico: slo programando se aprende a programar. Y


esto es an ms cierto, si cabe, en C++. El lector deber contar con un
compilador que le permita chequear el cdigo escrito: muchos
programadores de C se quedaran asombrados al ver la largusima letana de
errores y warnings que apareceran al compilar como C++ su cdigo C. La
creciente complejidad de las implementaciones C++ exige cada vez ms
requerimientos hardware. El lector necesitar, pues, de un compilador que
soporte, preferiblemente, la versin 3.0 del AT&T C++, a la vez que
mquina suficiente para soportarlo, junto con las pertinentes libreras de
clases, como ms adelante veremos.

QU OBJETIVO SE PERSIGUE?

Sorprende que en la iniciacin a cualquiera de los tpicos de OOP el


principiante siempre se encuentre con introducciones, prembulos e incluso
introducciones de introducciones. Bien, esto es desafortunadamente
irremediable: es necesario cambiar muchas cosas (entre ellas la "forma de
pensar" del programador) para poder aplicar eficientemente uno o dos
conceptos clave. Dado que se supone, sobre todo en C++ y Object Pascal,
que el interesado posee conocimientos previos de programacin
estructurada, buena parte del tiempo se emplea repitiendo: olvdese de
cmo lo estaba haciendo: piense en objetos! Esto es exactamente, pues, lo
que se pretende en este libro: sumerger al lector en un nuevo lenguaje
(C++) pero siempre desde el punto de vista de la OOP, lo que ayudara a
que profundizara ms tarde en los tpicos introducidos mediante el uso de
algunos de tantos excelentes textos sobre el tema. Se pretende, por tanto,

C++/OOP: UN ENFOQUE PRCTICO Pgina 4/297


guiar al principiante entre el oscurantismo y la verdadera complejidad de un
nuevo lenguaje y un novedoso (slo para l, por supuesto) paradigma: la
Programacin Orientada-a-Objetos. Pinsese que un no muy extenso detalle
sobre, por ejemplo, la caracterstica de templates (plantillas) de C++ ocupa
la mayor parte de un magnfico texto de Robert Murray sobre el lenguaje. Mi
objetivo es modesto: tras el ltimo captulo del libro (o quizs
afortunadamente antes) el lector debera ser capaz de desarrollar programas
relativamente simples en C++ que funcionaran bajo Microsoft Windows 3.1,
OSF/Motif, OS/2 2.1, MS Windows NT y Mac utilizando libreras comerciales
de clases (como, por ejemplo, Tools.h++, Codebase++) y entornos de
aplicacin (como ObjectWindows C++/Views).

BIBLIOGRAFA DISPONIBLE SOBRE C++

Lamentablemente existen contados libros originales en castellano sobre C++


y, por lo que yo conozco, actualmente se reducen a la introduccin
elemental al tema escrita por Francisco Javier Ceballos ("Introduccin a C++
y a la Programacin Orientada al Objeto"), referida bsicamente a la versin
1.2 de C++ (con destellos de la versin 2.0), lo cual, teniendo en cuenta
que actualmente se trabaja en base al cfront 3.0 de AT&T, la convierte en un
tanto desfasada; y al buen texto "Programacin en C++", de los hermanos
Enrique y Jos Hernndez Orallo, ajustado a AT&T 3.0 e incorporando
"plantillas" y manejo de excepciones. Naturalmente no cuento aqu con los
tpicos manuales de compiladores del tipo "Cmo manejar Borland C++ 4.0
en 1000 das", por razones obvias, as como tampoco con las traducciones
de las obras inglesas que, a poco, se irn introduciendo en el mercado. Los
manuales de la inmensa mayora de los compiladores comerciales de C++ y
el grueso de la bibliografa estn en ingls, y en U.S.A. se est produciendo
un verdadero "boom" editorial con la OOP que, a poco, veremos en Espaa.
Como, insisto, el tema es difcil, pospondr la relacin de material
bibliogrfico hasta el artculo final, en el que adems resear brevemente
tanto los libros como los compiladores, libreras de clases y entornos de
aplicacin ms interesantes.

C++/OOP: UN ENFOQUE PRCTICO Pgina 5/297


EL PROBLEMA DE LAS VERSIONES

Esta cuestin es indicativa de lo que en OOP es tnica general: la falta de


estandarizacin. Frecuentemente oiremos de las versiones 1.2, 2.0, 2.1 y 3.0
de C++. Existe, por otro lado, un comit ANSI dedicado a la estandarizacin
del lenguaje y que no entiende de tales numeraciones. Qu ocurre? Bueno,
como C++ fue creado por el Dr. Bjarne Stroustrup, de los laboratorios
AT&T Bell, y stos siempre se han mantenido en la vanguardia del lenguaje,
los compiladores comerciales de otras casas se han basado en la numeracin
de AT&T. El comit ANSI X3J16, creado para la estandarizacin de C++,
admiti en su da, por otro lado, el texto "Manual de Referencia C++
Anotado" (que en adelante denominaremos ARM, como es prctica comn
en los textos americanos) del Dr. Stroustrup y Margaret Ellis como documen-
to base del lenguaje, por lo que en puridad no cabe hablar de versiones del
lenguaje. No hay que olvidar, no obstante, que el nombrado comit ANSI ni
siquiera posee la cualificacin de internacional: a pesar del inters de sus
miembros, todava est circunscrito al mbito nacional estadounidense. El
mercado, por otra parte, sigue bsicamente la numeracin de AT&T, criterio
que por facilidad para distinguir entre distintas caractersticas del lenguaje yo
tambin adoptar en lo que sigue. Cabe destacar, al fin, que peridicamente
el comit ANSI X3J16 publica un borrador del estado actual del estndar del
lenguaje, y que se puede conseguir directamente de X3. Este borrador, que
es una suerte de ARM ampliado y consensuado, est afortunadamente
constituyndose en fuente y modelo de los compiladores comerciales (como
ocurre, por ejemplo, con Borland C++ 4.0).

ALGUNAS NOTAS SOBRE SIGLAS

Intimida abrir cualquier revista tcnica sobre OOP por la cantidad de siglas y
jerga incomprensible que aparece en cada pgina. Encontramos con
demasiada facilidad claves como OBCS, HSC, ODS, EER, etc. de difcil
traduccin. Qu ocurre? Vive la comunidad OOP en un mundo aparte?
Bien, la verdad es que s. No existen tcnicas estndares ni en anlisis ni en
diseo, por lo que investigadores y equipos privados desarrollan
continuamente tcnicas propias, frecuentemente sin ningn nexo comn;
junto con stas, naturalmente, desarrollan tambin sus propias siglas y
terminologas. En general podemos afirmar que O representa Objeto, OO
equivale a Orientado-a-Objetos, A vale por Anlisis y D por Diseo, R
significa Requerimientos, L representa Lenguaje y C suele equivaler a Clase.
De esta forma OOAR, por ejemplo, significa Requerimientos del Anlisis
Orientado-a-Objetos. Mi consejo es, de cualquier manera, que el lector no
d por asumidos significados intuitivos a siglas o conceptos cuya
procedencia no conozca, por muy elementales que stos parezcan.

C++/OOP: UN ENFOQUE PRCTICO Pgina 6/297


BREVSIMA HISTORIA DE C++

Bsicamente C++ es el resultado del acoplamiento al lenguaje C de algunas


(no todas) de las caractersticas de la OOP. Su creador, el hoy famoso Bjarne
Stroustrup, trabaj desde 1980 en lo que por aquel entonces se denominaba
"C con clases", directamente proveniente de una cierta simbiosis con el
lenguaje SIMULA. La publicacin de varios ensayos sobre el tema culmin
en 1986 con la publicacin por el Dr. Stroustrup de la obra "El Lenguaje de
Programacin C++", en la que se establecieron las bases del lenguaje como
hoy lo conocemos. Este texto ha representado para C++, aunque tal es pura
opinin, lo mismo que en su da represent para el lenguaje C el libro "El
lenguaje de Programacin C" de Kernighan & Ritchie. Actualmente se
encuentra disponible la segunda edicin de aqul texto, en la que se detalla
la versin 3.0 del lenguaje.

ES C++ UNA MERA EXTENSIN DE C?

El lector ya puede imaginarse que la respuesta es NO. Lo que puede que le


sorprenda es hasta qu punto son diferentes ambos lenguajes. Es frecuente
encontrarse con la afirmacin "C++ es un superconjunto de C", lo que
sugiere que todo el cdigo C podra compilarse como C++. En realidad esto
no funciona: nicamente un subconjunto de C, conocido como C-, cumple
esta condicin. Usar C++ como C puede ser, por otra parte, un error en
algunos casos. El lenguaje C, por su tremenda flexibilidad, es idneo para
ser extendido con relativa facilidad: han surgido as derivaciones de C como
C++, C//, Objective-C, etc., cada una con su propia idiosincrasia y
normativa. Esto sugiere que C ha servido, en definitiva, nicamente como
sustrato bsico sobre el que desarrollar nuevas tcnicas y metodologas.
C++ es, de cualquier forma, un mejor C. De ah proviene su nombre: C con
el operador de post-autoincremento.

OBJETOS: UNA NUEVA FORMA DE PENSAR

El trmino "objeto" se muestra omnipresente en la literatura de OOP, y sin


embargo es frecuente que el lector, tras leer diversas descripciones y
definiciones, termine el texto an ms confundido que al empezarlo. As, se
puede leer -correctamente- que "un objeto es una encapsulacin de datos y
de los mtodos para manipular a stos", o, en un nivel superior, que "es la
instancia de una clase, siendo sta la entidad conceptual que encapsula los
comportamientos comunes a los objetos que representa". Qu ocurre? Que
estas definiciones nicamente las entiende quien ya las entenda, pues al
resto les representa una relacin parecida a la que tienen, por ejemplo, las
funciones de variable complejas con los procedimientos hologrficos: o sea,
suponen que es verdad pero no les dice nada, como ocurre en la clebre
ancdota del economista.

C++/OOP: UN ENFOQUE PRCTICO Pgina 7/297


Qu es un objeto? Bien, aqu ocurre lo que con los conceptos "grupo",
"tomo" o "conjunto": la aplicacin del concepto general depende del nivel
de abstraccin elegido. Para no perdernos intentaremos una tcnica muy
usada en OOP: el smil antropomrfico. Relacionaremos, as, objetos reales
con el concepto en estudio. Antes de proseguir debemos, sin embargo,
desechar algo: el lector deber olvidar, y aun repudiar (en la mejor tradicin
bblica), cualquier asociacin de objetos en el mundo real con posibles
tcnicas de programacin por l conocidas. O sea: no debe pensar como
programador, ni siquiera en el ms elemental de los niveles. No debemos
asimilar, pues, los objetos como datos, ni como mtodos o servicios. Dicho
esto, detallemos algunos objetos del mundo real: una factura, una sala de
cine, un libro, un programa, el sol, un avin, etc. El lector aqu podra decir:
"bien, bien, esto son objetos, ya lo s, pero qu tiene esto que ver con la
programacin?". Paciencia, paciencia. Retengamos, por ahora, el esquema
ilustrado por el siguiente ejemplo: si usamos, verbigracia, un objeto "lupa"
con un objeto "sol" obtendremos el resultado de un rayo calrico que podra
incidir sobre un objeto "papel" quemndolo. Aqu vemos distintos objetos
interactuando entre s, con la siguiente interpretacin formalista: el "sol"
enva un mensaje luminoso a la "lupa", y sta enva un mensaje calrico al
"papel", que contesta (en el mundo real de forma irremediable) con un
mensaje a s mismo que dice "qumate". Examinemos el mismo esquema
desde otro enfoque: no es que existan unos datos puros que configuren los
objetos y unos servicios o mtodos etreos y ajenos a los objetos y que los
afecten. El "sol" no es un cmulo o estructura de datos, como no lo son el
"papel" o la "lupa". No existen, tampoco, mtodos o funciones ajenos a tales
objetos: que se queme el "papel" depende de las caractersticas intrnsecas
de ste, pues no existe una "funcin de quemado" general. Un momento!
No existe una nocin general de "quemar"? Ciertamente: tan cierto como
que la materia se compone de tomos, aunque tal conocimiento no nos sirva
de mucho. Nosotros no queremos "quemar" una abstraccin o un "objeto
general": deseamos "quemar" un "papel" y quin sabr ms de "quemar un
papel" que el "papel" mismo? Intentemos otro ejemplo: existe una funcin
general en los seres humanos para "dormir" y que en los espaoles se
concreta adems en la "siesta"? O es que la funcin de dormir especfica de
los espaoles incluye como individualidad la "siesta"? Parece que nuestra
intuicin se inclina por lo segundo.

Conservemos la intuicin y abordemos un objeto bien familiar: una ventana


de Microsoft Windows. Uno de tales objetos poseera unas determinadas
caractersticas internas (barra de ttulo, men, color, scroll-bars, etc.), a la
vez que unos servicios, mtodos o funciones especficas
(redimensionamiento, movimiento, iconizacin, etc.). En este contexto
aparece ms clara la comunicacin entre objetos: un click del ratn puede
originar un mensaje dirigido a la ventana que, a su vez, origine un mensaje
de la ventana a ella misma forzando su maximizacin. Hemos puesto en el
mismo saco a objetos, mtodos, datos y mensajes. Bien, retengamos de
momento esta amalgama de ideas.

C++/OOP: UN ENFOQUE PRCTICO Pgina 8/297


PROGRAMACIN ORIENTADA-A-OBJETOS

Cabra aclarar, antes de nada, que prefiero la expresin Orientacin-a-Obje-


tos frente a la tambin muy usada Orientacin-al-Objeto. Es una matizacin
semntica en la traduccin del trmino ingls, que pierde la riqueza
contextual de la adjetivacin en la traduccin literal. Dicho esto, entremos en
el ncleo del asunto: qu demonios es la OOP (Object-Oriented
Programming)?

La OOP es, simplificando, un sistema de programacin que utiliza objetos


para la resolucin de problemas, interrelacionndolos mediante mensajes.
Imaginemos, por ejemplo, que queremos desarrollar una aplicacin para la
gestin organizativa de un despacho profesional. En la vida real nos
encontraramos con directores, secretarias, mquinas de escribir,
impresoras, etc. Una posible solucin en OOP sera trasladar estos objetos al
espacio de programacin: el objeto telfono, al sonar, lanzara un mensaje
primero al centro de control y luego a una determinada secretaria
mecanizada, la cual, en calidad de objeto, podra responder reteniendo
brevemente la llamada y enviando un mensaje de aviso a la operadora
correcta. De nuevo vemos que no nos movemos con esquemas funcionales
derivativos: no existe una funcin general de llamada telefnica (pueden
existir telfonos digitales, centralitas, etc.). Modelamos nuestra visin del
problema a travs de objetos, que se relacionan entre s por pocos canales,
de forma que la comunicacin de mensajes se pretende alta y clara. En
definitiva se pretende que las ligazones entre distintos objetos (o entidades)
sean transparentes para el observador exterior. Naturalmente esto favorece
sobremanera la comunicacin entre los responsables de las distintas etapas
de desarrollo de software.

Un ejemplo de uso habitual podra ser el de los gestores de bases de datos


relacionales. Desde la ptica de la OOP los ficheros de tipo, por ejemplo,
*.DBF, tan comunes en el mundo PC, se asocian a objetos "dataBaseFile",
mientras que los ndices (del tipo *.NDX *.MDX) se asocian a objetos del
tipo "indexFile". La ordenacin de una base de datos consistira en un
mensaje que un objeto "indexFile" dirigira a un objeto "dataBaseFile" del
tipo "ordnate en base al ndice con etiqueta X". El habitual comando "open"
se convierte, as, en un mensaje que se dirige al objeto "dataBaseFile",
forzando la apertura del fichero de base de datos. Naturalmente la extensin
lgica de estos conceptos nos llevara a superar el concepto de bases de
datos relacionales, entrando en lo que se ha dado en denominar ODBMS
(Sistemas Gestores de Bases de Datos de Objetos), conceptualmente
diferenciadas de aqullas.

C++/OOP: UN ENFOQUE PRCTICO Pgina 9/297


HA MUERTO LA PROGRAMACIN ESTRUCTURADA?

Definitivamente NO. Al menos no todava. En realidad la OOP podra


considerarse como una extensin natural de la programacin estructurada,
dado que, en definitiva, aqulla surgi debido a las carencias y debilidades
de sta. Permitmonos un breve repaso histrico: por all por los aos 70
una nueva metodologa denominada "desarrollo estructurado", basada en la
independencia de datos y funciones o mtodos, permiti superar la en aquel
entonces llamada "crisis del software". As como el nombre de Timothy
Leary qued indeleblemente unido al movimiento hippie, el diseo
"top-down" se convirti en sinnimo de enfoque estructurado: un problema
se asimila a una funcin o procedimiento, que se descompone en problemas
ms pequeos, que a su vez se descomponen en otras funciones ms
pequeas, hasta llegar a problemas descomponibles. Gracias a estos
mtodos los sistemas software fueron poco a poco creciendo en tamao y
complejidad, de forma que al final los problemas solucionados por el
desarrollo estructurado generaron nuevos problemas de ms difcil solucin.
A principios de los aos 80 las empresas recabaron en que buena parte de
su presupuesto se dilapidaba en el mantenimiento de verdaderas monstruo-
sidades software: las labores de depuracin se volvan ms costosas segn
iba aumentando el tamao de los programas; adems, cualquier modifica-
cin repercuta normalmente en todos los mdulos, de manera que un
programa bien chequeado pasaba a ser, despus de una pequea reforma,
motivo de nuevos y largos testeos y validaciones de estabilidad. El desarrollo
de libreras, potenciado sobremanera, haba llegado a un punto crtico de
ineficacia: si una funcin se ajusta exactamente a lo que queremos se usar;
si no, habr que codificarla de nuevo. Todo el tiempo que el equipo de
desarrollo pudiera dedicar a la prototipacin y pruebas de un sistema se
perda, por otra parte, inevitablemente, debido a las escasas posibilidades de
reutilizacin del cdigo.

El panorama no parece muy alentador. Para intentar salir de este crculo


vicioso surgen en un primer momento lenguajes modulares como Ada,
Modula-2, etc., que solucionan algunos de los problemas planteados. La
situacin es, sin embargo, todava insatisfactoria, as que la lgica evolucin
de los conceptos de modularidad y reutilizacin del cdigo origina un nuevo
paradigma que se llamar OOP.

Hay que pensar, pues, que la OOP es un escaln muy avanzado en el


desarrollo de la programacin estructurada, pero no tan avanzado que le
permita prescindir de sta. De hecho muchos autores de la comunidad OOP
opinan que habra que tender a un ms fliz acercamiento entre ambas
metodologas.

C++/OOP: UN ENFOQUE PRCTICO Pgina 10/297


POR QU C++?

Es C++ el mejor OOPL (Lenguaje de OOP)? Qu tiene C++ que no


tengan los dems OOPL's que no tenga C? Es C++ un montaje de
marketing por el que se nos intenta vender como nuevos los viejos
esquemas, una vez remozados? En definitiva, por qu C++ y no otro
lenguaje?

Bien. Hemos visto en el apartado anterior que la OOP intenta solucionar los
problemas originados por el "enfoque estructurado". Todo esto est muy
bien, pero estas nuevas tcnicas necesitan de lenguajes de programacin
adecuados. Surgieron, as, lenguajes de nueva creacin como Smalltalk,
Eiffel, Actor, etc. Por otro lado se intent dotar de extensiones
Orientadas-a-Objetos a los lenguajes clsicos ms importantes, como C,
Pascal, Fortran, Ada, Cobol, Lisp, etc., originando C++, Object Pascal,
CLOS, etc.

Naturalmente hay que escoger: ventajas y desventajas. Los lenguajes


Orientados-a-Objetos "puros" permiten una enorme flexibilidad, aunque casi
siempre en detrimento de la eficacia. Pensemos que en Smalltalk, por
ejemplo, no existen tipos predefinidos en el sentido que conocemos: el tipo
es una mera equiqueta pegada a un objeto y que, en un momento dado,
podemos cambiar, pegndole otra distinta. As, por ejemplo, un dato
cambiara de tipo en el transcurso del programa: algo inpensable en C y aun
en C++. Por otra parte, la ligadura de un mensaje dirigido a un objeto con
una respuesta o mtodo determinado se produce siempre, en Smalltalk, en
tiempo de ejecucin: es decir, no sabemos en tiempo de compilacin qu
cuerpo de una determinada "funcin" se ejecutar al ejecutar el programa
(notemos que la misma "funcin" o "mtodo" puede tener distintos cuerpos
en distintos objetos). Naturalmente, como el lector inmediatamente habr
adivinado, esto origina no pocos problemas en la depuracin y testeo de las
aplicaciones, a la par que dificulta enormemente la captura de errores en
tiempo de ejecucin (pensemos en qu ocurre cuando dirigimos un mensaje
a un objeto equivocado -o sea, no cualificado para responderlo-: el desas-
tre!). Los objetos, en Smalltalk, suelen, por tanto, incorporar su propio
"depurador". Debo significar que en la actualidad Smalltalk est siendo muy
usado en prototipacin rpida y, aunque existen compiladores que permiten
la creacin de archivos ejecutables (como Smalltalk/V de Digitalk), en
general los entornos Smalltalk funcionan como intrpretes, con la prdida de
eficacia que esto supone en el plano comercial.

Un nivel aceptable de compromiso es el proporcionado por el lenguaje


"puro" Eiffel, creado (o ms bien publicitado) por el Doctor Bertrand Meyer
en 1.988 y recientemente adoptado para distintos proyectos por la Agencia
Espacial Europea. Este lenguaje enfatiza, entre otras interesantsimas
caractersticas, el uso de PRECONDICIONES y POSTCONDICIONES, algo
que poco a poco se ha ido incorporando a C++, como se puede observar en
muchas de las libreras comerciales actuales.

C++/OOP: UN ENFOQUE PRCTICO Pgina 11/297


C++, en contra de lo expuesto y como ya se coment anteriormente, es un
lenguaje hbrido: ha adoptado cuantas caractersticas de OOP no
perjudicaran su efectividad. As, por ejemplo, la ligadura dinmica de un
mensaje a un mtodo se ha implementado (mediante las denominadas
funciones virtuales) de forma que soporta un chequeo de tipos en tiempo de
compilacin. Se tiene, por un lado, una mejora sustancial de las capacidades
de C, a la vez que se obtiene buena parte de lo mejor de la OOP sin perder
aquellos beneficios. Es lo mejor de dos mundos, como suele decirse. Un
poco ms vehemente, el Doctor Stroustrup ha llegado a afirmar que C++ es
un lenguaje destinado a "los programadores serios". No quiere esto decir,
sin embargo, que otros lenguajes no sean "serios", sino que ste difcilmente
podra ser utilizado por programadores aficionados o de fin de semana

Retomando la pregunta inicial, vayamos al grano: escogemos C++, entre


otros lenguajes, por sus caractersticas conjuntas de robustez, eficacia y
flexibilidad. El mercado industrial ha efectuado ya, por otra parte, su
decisin y ha convertido a C++ en el estndar industrial de facto en OOP,
con todo lo que esto conlleva (pensemos, por ejemplo, en el arrinconamien-
to comercial del sistema de video Beta, posiblemente mejor que el VHS pero
rotundo perdedor en el mercado ante ste). As, poco a poco, infinidad de
firmas han ido desarrollando extensiones, libreras, compiladores, entornos
de aplicacin y herramientas para C++.

C++/OOP: UN ENFOQUE PRCTICO Pgina 12/297


ES LA OOP EL VRTICE DE LA PROGRAMACIN?

Hemos llegado al lmite de las posibilidades de los sistemas de


programacin? O de otra forma: Es la OOP el no va ms del sumum? Bien,
slo un fantico podra contestar afirmativamente (y el autor podra hablar
durante horas de este tipo de cerrazn mental). La OOP es nicamente un
estadio en la evolucin de las tcnicas de programacin, aunque quiz en
estos momentos sea el estadio ms avanzado. Pero en informtica, an ms
rpidamente que en otros campos, no existen metodologas inamovibles,
sino nicamente peldaos en una de tantas escaleras. Tal vez la idea de los
vasos comunicados fuera un buen smil: distintas tecnologas despuntan
individualmente en una primera fase para inmediatamente despus
converger en metodologas conjuntas. Veamos, por ejemplo, lo que ha
sucedido con las tcnicas CASE, que en su momento parecieron conducir los
esquemas de desarrollo soft a un punto de no-retorno. ltimamente la
comercializacin de dichas herramientas se ha reducido de forma drstica: la
eclosin de la OOP y de los entornos grficos, entre otras razones, ha
causado una cierta suspensin, hace pocos aos impensable, de la
metodologa CASE. Qu es lo que, en definitiva, ha sucedido despus? Las
tcnicas CASE y de OOP estn lentamente unindose: ya se habla de
herramientas CASE con extensiones OOP, o aun de entornos de aplicacin
OOP con utillera CASE, apareciendo, as, herramientas OO-I-CASE,
OOCASE, etc. Al final la practicidad se une a la brevedad: "lo mejor" incre-
mentalmente se fusiona con "lo bueno", y la industria sigue avanzando.

C++/OOP: UN ENFOQUE PRCTICO Pgina 13/297


OOP:
2
CONCEPTOS BSICOS

Examinadas, aunque sucintamente, las ideas que subyacen bajo el nuevo


"paradigma de objetos", vamos a abordar los conceptos que formalmente
definen una metodologa como Orientada-a-Objetos. Un sistema se califica
como Orientado-a-Objetos cuando rene las caractersticas1 de: abstraccin,
encapsulacin, herencia y polimorfismo. Antes de abordar estas cualidades
repasaremos, no obstante, los conceptos bsicos que las informan: objetos,
mensajes, clases, instancias y mtodos. Debo hacer hincapi, de nuevo, en
que tales conceptos debern ser considerados por el lector con un "espritu
vaco de prejuicios" y sin pensar, en esta primera fase, en su inmediata
aplicacin a la programacin. Una advertencia al lector: conozco a algunas
personas que no han podido pasar de ser 'lectores de introducciones',
porque stas les dejan en un estado similar al que tenan antes de leerlas. Es
difcil conjugar facilidad de lectura y amenidad con rigurosidad y formalismo
prctico. Indudablemente, por otro lado, conforme vayamos avanzando
(porque lo que aqu se pretende es, ante todo, avanzar) los conceptos se
tornarn ms complejos y dependern en buena medida de lo que hayamos
visto anteriormente. No hay ms remedio, entonces que resignarse y releer,
releer como norma, pues, como ya ha quedado dicho, C++ es un lenguaje
dficil para programadores serios. Entremos a saco, sin ms prembulos, en
la esencia terica de la OOP, con el fardo de terminologa que esto conlleva,
atacando, as, la parte "correosa" del tema.

OOP: CONCEPTOS TERICOS BSICOS

1
Lo cierto es que a estas alturas subsisten importantes diferencias de criterio
entre distintos autores a la hora de establecer los pilares en que se apoya la
Programacin Orientada-a-Objetos. El mismo autor del presente texto sostiene, por
ejemplo, que la herencia no es una caracterstica bsica del paradigma, sino ms
bien un mecanismo que permite la implementacin de jerarquas polim rficas.
Intentanto ser lo ms general posible, he reflejado, no obstante, las caractersticas
ms comunmente aceptadas por el grueso de expertos en estas reas.

C++/OOP: UN ENFOQUE PRCTICO Pgina 14/297


Un objeto es una encapsulacin abstracta de informacin, junto con los
mtodos o procedimientos para manipularla. Segn la esquemtica
definicin de Wegner, "un objeto contiene operaciones que definen su
comportamiento y variables que definen su estado entre las llamadas a las
operaciones". Bueno, pensemos en un ejemplo asequible: una sala de cine.
Imaginemos un objeto del tipo "sala de cine" donde los datos, variables o
informacin estaran constituidos por los espectadores; imaginemos tambin
(y esto es importante) que los espectadores no pudieran moverse por s
solos dentro de la sala (como si estuvieran catatnicos). Quines seran los
manipuladores, mtodos, procedimientos u operaciones encargados de
manipular a los espectadores? Indudablemente los acomodadores de la sala,
aunque tambin el personal directivo de la misma.

Un mensaje representa una accin a tomar por un determinado objeto. En


el ejemplo anterior, la orden "que comience la proyeccin de la pelcula"
podra ser un mensaje dirigido a un objeto del tipo "sala de cine". Otro
mensaje podra ser la orden de "desalojo de la sala" en funciones
no-continuas. Notemos que el mensaje se refiere nicamente a la orden, y
no tiene que ver con la forma como sta es respondida. En C++ un mensaje
equivale al PROTOTIPO de una funcin miembro en la descripcin de una
clase. O sea, si pensamos en una posible funcin "especialmente restringida
a un objeto" (que en C++ se denomina funcin miembro) tal como
"empezarProyeccion", el mensaje estara representado por el prototipo de tal funcin, y no por su definicin o
implementacin especfica.

Una clase equivale a la generalizacin o abstraccin de un tipo especfico de


objetos. Los polgonos con tres lados iguales podran ser generalizados, por
ejemplo, en una clase que llamaremos "trianguloEquilatero". Hablar de clase es, as, hablar de
una determinada clase de objetos. En C++ una clase ("class", segn la terminologa del lenguaje) es un tipo
definido-por-el-usuario. De esta forma, y siguiendo con el ejemplo cinematogrfico, el objeto abstracto "Sala de
Cine" sera implementado como la clase "SalaDeCine" en C++, habiendo definido as un nuevo tipo de dato
abstracto, que ser manejado por el compilador de parecida forma a como lo hace con los tipos predefinidos (int,
char, float, etc.). Podremos, as, como ya veremos, declarar un determinado objeto del tipo (o de la clase)
"SalaDeCine".

Una instancia es la concreccin de una clase. Una instancia de la clase


"SalaDeCine" sera, por ejemplo, el objeto "cineParadox". El concepto de instancia, en realidad, une la nocin de
objeto con la de clase. Esto quiere decir que en el esquema de OOP, a semejanza de lo que ocurre en el mundo
real (aunque normalmente de forma inadvertida), debemos identificar en primer lugar una abstraccin general,
delimitar las caractersticas comunes de un grupo de objetos, y luego poner nombre (o identificar especialmente)
a uno o ms de ellos. Tomemos, verbigracia, el nmero "1/7": lo primero que se nos ocurre es que tal es un
nmero "racional". En OOP diramos que es un objeto o instancia de una clase de objetos denominada "Racional",
cuyas caractersticas seran las que conocemos (a nivel de programacin) de tal conjunto.

Un mtodo consiste en la implementacin en una clase de un protocolo de


respuesta a los mensajes dirigidos a los objetos de la misma. La respuesta a
tales mensajes puede incluir el envo por el mtodo de mensajes al propio
objeto y aun a otros, tambin como el cambio del estado interno del objeto.
En C++ los mtodos estn implementados como DEFINICIONES de
funciones miembro de una clase, representando el conjunto de mensajes al

C++/OOP: UN ENFOQUE PRCTICO Pgina 15/297


que los objetos de tal clase pueden responder. Revisando el ejemplo que
examinbamos al hablar de "mensajes", podemos decir que la respuesta al
mensaje "empezar la Proyeccin" podra significarse en la serie de
instrucciones tendentes a efectuar la accin de visionado de la pelcula:
apagar las luces, encender el proyector, etc. Estamos hablando, como el
lector habr adivinado, del cuerpo de la funcin miembro "empezarProyeccion".

ENVO DE MENSAJES A OBJETOS

Reexaminemos los conceptos intuitivos expuestos hasta ahora. Hemos visto


que mientras que la programacin estructurada se basa, sustancialmente, en
llamadas de alto nivel a determinadas funciones y rutinas, la OOP consiste,
bsicamente, en las relaciones entre objetos (instancias de clases en C++)
que responden a mensajes de acuerdo con los mtodos (funciones miembro
en C++) establecidos en el protocolo de descripcin de sus respectivas
clases. Bueno, esto se va complicando, pero es inevitable: no se puede evitar
indefinidamente la endiablada terminologa "de objetos". Y no hemos hecho
ms que empezar. Ok: prosigamos.

El "envo de un mensaje" a un objeto equivale, en C++, como ya he


indicado, a una llamada a una funcin miembro de la clase correspondiente
al objeto. En realidad las expresiones "mtodos", "envo de mensajes",
"instancias de variables", etc. pertenecen originariamente al lenguaje
Smalltalk. Volvamos al ejemplo del cine y "lancemos mensajes":

SalaDeCine cineParadox;
SalaDeCine *punteroASalaDeCine;

Hemos declarado por un lado un objeto denominado cineParadox, del tipo


definido-por-el-usuario SalaDeCine. Seguidamente hemos declarado un puntero al mismo tipo de dato abstracto.
El mensaje "que comience la proyeccin de la pelcula", dirigido a la sala "Cine Paradox", podra ser codificado as:

cineParadox.empezarProyeccion();

Esto es, el mensaje o llamada de funcin se ha dirigido directamente al


objeto. Pero veamos el siguiente cdigo:

punteroASalaDeCine = new SalaDeCine( cinePalafox );


punteroASalaDeCine->empezarProyeccion;

En este ejemplo primero dirigimos el puntero declarado anteriormente hacia


un objeto de nueva creacin, alojado en la memoria de almacenamiento
libre mediante el operador new (podra decirse que una mejora sustancial
sobre la conocidad funcin malloc de C), del tipo SalaDeCine, al que hemos denominado
(como variable en el fondo que es) cinePalafox. Seguidamente el mensaje es dirigido al objeto cinePalafox por
mediacin de tal puntero al mismo.

C++/OOP: UN ENFOQUE PRCTICO Pgina 16/297


Vemos, pues, que la notacin '.' sirve para enviar mensajes directamente a
un objeto, mientras que '->' se usa para el envo de mensajes a los objetos a
travs de punteros que apunten a los mismos.

Cabra notar aqu, redundando en lo apuntado anteriormente, que el


prototipo de funcin empezarProyeccion() corresponde al mensaje, mientras que el mtodo estara
constituido por la implementacin de la definicin de la funcin miembro empezarProyeccion(). El mensaje corres-
ponde al "qu", mientras que el mtodo corresponde al "cmo". As, el mtodo podra definirse -en la clase
SalaDeCine- de la forma:

void SalaDeCine::empezarProyeccion()
{
// apaga msica y luces y comienza proyeccin
ponerMusicaDeFondo( OFF );
ponerLucesSala( OFF );
marchaProyector( ON );
}

En este caso el mensaje empezarProyeccion podra ser enviado al objeto cineParadox por un objeto
cronmetro con un horario determinado.

Advertimos en el cdigo, en lo que parece la definicin de una funcin, una


sintaxis extraa: si suprimiramos la parte "SalaDeCine::" nos quedara, aparentamente,
una porcin de cdigo habitual en C. Esta porcin "extra" nos sugiere que, de alguna forma que ms adelante
veremos en detalle, la funcin void empezarProyeccion(void) "pertenece" o "est circunscrita" a la clase
SalaDeCine. El cdigo indica, en definitiva, que estamos definiendo una funcin miembro de la clase
SalaDeCine. Hemos visto, tambin, unas notaciones conocidas para los programadores de C por su uso en
"structs": "." y "->". En efecto, las clases en C++ son, en cierta forma, una extensin de los structs (que,
ampliados, tambin existen en C++). Debemos pensar, no obstante, ms en la conceptualizacin como mensajes
que como sintaxis de acceso a datos, cual sera ms propio de C.

Pudiera parecer, por otra parte, que el mtodo o funcin miembro


empezarProyeccion est compuesto por funciones del tipo usado en programacin estructurada, pero en realidad
se trata de mensajes dirigidos al mismo objeto (en este caso concreto, cineParadox), dado que, por ejemplo, el
cdigo

marchaProyector( ON );

equivale a

this->marchaProyector( ON );

en donde this es un puntero implcito al objeto cineParadox que, como veremos ms


adelante, "prefija" a todas las funciones miembro no estticas. As, y segn lo visto antes, el mensaje de poner
en marcha el proyector cinematogrfico es enviado al objeto por medio de un puntero al mismo. Pero, un
momento, un momento: Funciones miembro estticas? Punteros implcitos? Una funcin que primero se declara
() y ms tarde se repite como (void)? De acuerdo, de acuerdo: esto no es fcil. Lo malo de C++ (como de
cualquier OOPL) es que muchos de los conceptos expuestos debern ser retenidos vagamente en primera
instancia para poder, luego, con ms bagaje, volver a ellos para entenderlos mejor. Significa esto que el lector
se convertir en un re-lector? Pues, efectivamente, s: en C++ hay que volver a leer muchas veces lo ya ledo,
pues se parte de una idea bsica: slo se entender bien un concepto de C++/OOP si ya se entenda
razonablemente bien antes de abordarlo. Pero esto es ridculo!, propondr el lector. Bien, slo puedo responder:

C++/OOP: UN ENFOQUE PRCTICO Pgina 17/297


"esto es lo que hay". Intenten, si no, explicar a qu sabe el vinagre a quien no lo haya ya probado. Volvamos,
pues, a nuestros objetos.

Hemos visto, pues, que el objeto cineParadox responde al mensaje de iniciar la sesin envindose
a s mismo distintos mensajes de control, que a su vez enviarn otros mensajes (a ste u otros objetos) o
manipularn sus propios datos internos. Aparece ahora claro que, en general, el envo de un mensaje a un objeto
que carezca del mtodo para responderlo, por s mismo directamente o mediante herencia o delegacin, ser
calificado, en C++, como un error en tiempo de compilacin: lo mismo que ocurrira si llamramos en C a una
funcin inexistente.

ABSTRACCIN

En qu consiste la abstraccin? Bueno, por decirlo sencillamente, esta es


una cualidad de los seres humanos que les permite encontrar lo bsico que
hay de comn en una serie de objetos. Imaginmonos, por ejemplo, a un
nio frente a una cesta de fruta: aunque nunca antes hubiera visto esos
objetos, en muy poco tiempo podra separar las frutas del mismo tipo. El
nio pondra en un montoncito los pltanos, en otro las naranjas, en otro las
ciruelas, etc. Pero esto es una bobada!, podra exclamar el lector. Ni mucho
menos, seor lector! podra en derecho exclamar entonces yo. La cosa no es
nada fcil. Lo que ocurre es que como esta capacidad nos ha acompaado
siempre desde nios, no nos damos cuenta cuando la usamos. Es como
andar erguido sobre dos piernas: de fcil nada. Y si no que se lo
pregunenten a los elefantes del circo o a los fisilogos. Bien. En definitiva
por medio de la abstraccin conseguimos no pararnos en el detalle concreto
de las cosas (si una fruta tiene mayor o menor tamao, si tiene picaduras de
aves, etc.), pudiendo as generalizar y operar con "entes abstractos". O sea,
cuando decimos "a m me gustan las naranjas" no queremos decir que nos
gustan "todas" las naranjas, de cualquier tipo y en cualquier estado, sino que
indicamos que nos gusta el sabor, la textura, el aroma que por lo general
poseen las naranjas. Vemos, de esta manera, que el fenmeno de la
abstraccin consiste en la generalizacin conceptual de un determinado
conjunto de objetos y de sus atributos y propiedades, dejando en un
segundo trmino los detalles concretos de cada objeto. Qu se consigue
con la abstraccin? Bueno, bsicamente pasar del plano material (cosas que
se tocan) al plano mental (cosas que se piensan). Tambin hemos
conseguido volar, construir edificios, la receta de los canelonni Rossini, la
bomba de neutrones, etc. Pero qu tiene que ver esto con la OOP? Bien:
imaginemos una abstraccin sobre un grupo de tornillos. Una vez que
descubramos lo que distingue a un tornillo de otros objetos, podremos
reconocerlo con cierta facilidad en cualquier sitio, de forma que si
aprendemos a manejar un destornillador con un tornillo, lo podremos
manejar con cualquier otro, aunque todava no lo hayamos visto! Pensemos
ahora en la OOP: imaginemos que, de alguna forma, hemos podido inocular
la capacidad de abstraccin a un sistema software. Imaginemos
seguidamente que tal sistema rene las caractersticas bsicas y
funcionamiento de, por ejemplo, varias circuitos lgicos en una estructura
(que en C++ se llama class). Si codificamos sobre estas caractersticas

C++/OOP: UN ENFOQUE PRCTICO Pgina 18/297


bsicas, en el momento en que queramos introducir un circuito lgico
concreto, el trabajo ser mnimo o nulo. Bueno, ya iremos viviendo en carne
las ventajas de este fenmeno.

ENCAPSULACIN

Qu es, por otro lado, la encapsulacin? Lo que de inmediato nos sugiere


el vocablo es la accin de encapsular, de encerrar algo en una cpsula, como
los polvitos medicinales que nos venden en las farmacias. Podemos decir
que la encapsulacin se refiere a la capacidad de agrupar y condensar en
un entorno con lmites bien-definidos distintos elementos. El fenmeno de la
encapsulacin podra darse, por ejemplo, en el conjunto de herramientas y
elementos heterogneos que integran -es un decir- la caja de herramientas
de un fontanero: la caja es la cpsula y todo lo que haya dentro es lo que
hemos encapsulado. Pero no nos perdamos: la cualidad de "encapsulacin"
la aplicaremos nicamente a abstracciones: o sea, afirmar que una caja de
herramientas concreta encapsula un bocadillo, un martillo y un limn
constituye una cierta trasgresin lxica. Cuando hablemos de encapsulacin
en general siempre nos referiremos, pues, a encapsulacin abstracta. Las
dos propiedades expuestas estn, como vemos y en lo que a la OOP
concierne, fuertemente ligadas. Dicho de manera informal, primero
generalizamos (la abstraccin) y luego decimos: la generalizacin est bien,
pero dentro de un cierto orden: hay que poner lmites (la encapsulacin), y
dentro de esos lmites vamos a meter, a saco, todo lo relacionado con lo
abstrado: no slo datos, sino tambin mtodos, comportamientos, etc.
Pero, bueno y esto es OOP? Pues s, esta es una caracterstica fundamental
de OOP. Vemoslo de esta manera: imaginemos una base de datos
relacional conteniendo unos datos accesibles mediante programas en C
Pascal. Podramos decir (con cierto recelo) que la estructura de la Base de
Datos encapsula la informacin, pero qu podramos decir de las funciones
que se aplican a estos datos? Bueno, lo cierto es que tales funciones o
mtodos estarn normalmente dispersados en multitud de mdulos. En
realidad aqu se trata de una encapsulacin a medias y esto parece que no
funciona del todo bien: pensemos en unas cpsulas medicinales que
contuvieran nicamente algn componente qumico del medicamento y el
resto estuviera todo mezclado -en polvo- en el fondo de la caja: quizs al
final surtan el mismo efecto, pero, diantre, esto equivaldra a triturar junta
toda la comida de un da pensando que, de todas formas, en el estmago se
habr de juntar. Bien: la OOP proclama que una misma cpsula contenga
datos y funciones. Pero, cmo?, preguntar el inquieto lector. Pensemos en
Bases de Datos. Mejor en Bases de Objetos: aqu se archivan a la vez los
datos y los mtodos para accederlos, manipularlos, etc. Se archivan las
funciones? Efectivamente! Podemos comandar un query en SQL (mejor en
OSQL: SQL de Objetos) que seleccione y ejecute algunas de las funciones
archivadas a la vez que se aplica sobre los datos. Al meter en el mismo
"saco" (que en C++, como inmediatamente vamos a ver, es la class) datos y
funciones s que tenemos una encapsulacin "completa".

C++/OOP: UN ENFOQUE PRCTICO Pgina 19/297


ABSTRACCIN Y ENCAPSULACIN EN C++

En lo que a C++ se refiere, la unidad de abstraccin y encapsulacin est


representada por la Clase, (class). Por un lado es una abstraccin pues, de
acuerdo con la definicin establecida anteriormente, es en sta donde se
definen las propiedades y atributos genricos de determinados objetos con
caractersticas comunes (recordemos el ejemplo de la sala de cine). La Clase
es, por otro lado, una encapsulacin porque constituye una cpsula o saco
que encierra y amalgama de forma clara tanto los datos de que constan los
objetos como los procedimientos que permiten manipularlos. Las Clases se
constituyen, as, en abstracciones encapsuladas.

En principio, y a efectos de necesaria referencia para el manejo de lo que


sigue, adelantaremos una esquemtica definicin de "clase":

Una class en C++ es como un struct del tipo usado en C que admite en su
cuerpo tanto variables como funciones, ms unas etiquetas que controlan el
acceso a ambas.

Abordemos ahora el tema en su parte cruda y echmosle un vistazo a parte


del cdigo de lo que podra ser una descripcin de una clase que
denominaremos Racional:

Class Racional {
friend Racional& operator+( Racional, Racional );
// ...
private:
int numerador;
int denominador;
// ...
void simplificaFraccion();
// ...
public:
// ...
void estableceNumerador( int );
void estableceDenominador( int );
// ...
};

Observemos que en la clase Racional, abstraccin del conjunto de los nmeros racionales, de su
representacin formal (x/y: equis dividido por y) y operaciones (a/b + c/d), aparecen encapsulados tanto los
datos internos (numerador, denominador) como las funciones miembro para el tratamiento de stos (el operador
suma de quebrados, el procedimiento para cambiar el denominador, el mtodo para simplificar fracciones, etc.).

Bueno, la verdad es que en el cdigo anterior observamos muchas ms


cosas: el smbolo de comentario //, que anula lo escrito (a efectos de compilacin) desde su
aparicin hasta el fin de la lnea; las etiquetas private y public, que determinan cmo y quin acceder a los datos
y funciones de la clase; la palabra clave friend, que indica la calificacin como "amiga de la clase Racional" de una
funcin que suma Racionales (Racionales? alguna forma de typedef?) y a la que se dar el tratamiento de

C++/OOP: UN ENFOQUE PRCTICO Pgina 20/297


"operador suma" (operator +), devolviendo una "referencia" (&) a Racional (referencia a Racional? alguna
forma de puntero?). Vale, vale: son muchas cosas en las que todava no podemos entrar y que en su momento
veremos en detalle. Bstenos saber, por ahora, que tal cdigo intenta describir el comportamiento y la esencia de
un tipo de datos bien conocido por todos (el conjunto de los nmeros racionales, de la forma x/y, donde x e y
2
son nmeros enteros) pero no incluido como tipo predefinido por la mayora de compiladores. Esta carencia es la
que intentamos suplir con el cdigo expuesto: una vez descrita la clase (class), se habr traspasado al compilador
el conocimiento de, para l, un nuevo tipo de nmero: el "Racional". Retengamos de momento nicamente esta
ltima idea pensando, como nico alivio, que cuanto ms veamos una "rareza", antes dejara sta de serlo.

Salvado el inciso, quiz puedan apreciarse mejor las caractersticas de


abstraccin y encapsulacin notadas en la siguiente porcin de la definicin
de la clase SalaDeCine, tomada del ejemplo "cinematogrfico":

Class SalaDeCine {
private:
int aforoSala;
char *nombreSala;
// ...
public:
void alarmaEnCasoDeIncendio();
void empezarSesion();
// ...
};

La clase SalaDeCine aglutina, segn lo expuesto, tanto los datos correspondientes a las salas de cine en
general (capacidad de la sala, nombre de la misma, etc.) como los mtodos de respuesta a los mensajes dirigidos
a los objetos "salas de cine" (alarma por incendio, etc.). O sea, la clase SalaDeCine es la abstraccin resultante
de la asimilacin intelectual de todas las posibles salas de cine, vistas o soadas: aqu tenemos la cualidad de
abstraccin. La misma clase encapsula, por otro lado, datos y funciones dentro de un lmite bien-definido: cul?
las paredes del edificio? No, vaya! El lmite es el mismo que el que separa una sala de cine de un almacn de
frutas: su propia definicin, que en este claso coincide con el bloque de la clase. La cpsula esta formada por los
brazos {} que encierran el cuerpo de la clase.

Conviene notar que los tipos de datos incorporados al compilador (int, char,
long, etc.) son realmente abstracciones encapsuladas de datos y mtodos.
Consideremos el siguiente cdigo:

float resta, minuendo, sustraendo;


resta = minuendo - sustraendo;

El operador (-) representa aqu la operacin de substraccin entre dos


variables del tipo predefinido 'float'. Pero tal operador es, realmente, un mensaje que el objeto
sustraendo (un nmero float) le enva al objeto minuendo (otro nmero float) , y el mtodo de respuesta a tal
mensaje est implementado en la encapsulacin de tipo float predefinida en el compilador, que resta del
minuendo el sustraendo y devuelve un valor de tipo float. Tal mtodo es formalmente diferente del que podra
ser implementado para la resta de dos variables de tipo int (no hay parte decimal, etc.), o an de un tipo defini-
do-por-el-usuario (user-defined). Aclaremos esto: el compilador sabe que una operacin sobre floats no tiene por

2
Cuando hablo de tipos predefinidos o incorporados me refiero a los tipos de
datos que el compilador, tal y como sale de su envoltorio original, reconoce: char,
int, long, short, double, float, etc.

C++/OOP: UN ENFOQUE PRCTICO Pgina 21/297


qu ser igual a otra sobre ints. Realmente el compilador lo que hace es "colocar" junto a la definicin de un tipo de
nmero (en este caso el float) las "funciones" especficas para operar con los mismos: prcticamente lo que antes
hemos hecho "a mano" mediante las clases (recordemos la clase Racional).

Un momento!: demos tiempo a que se asienten las ideas. Primero est el


mensaje: hasta ahora pareca que mensaje equivala a funcin, a un tipo
especfico de funcin (funcin miembro). Vemos, sin embargo, que aqu la
funcin es sustituida por un operador de substraccin. Digamos, por el
momento, que un operador es una funcin con una disposicin no ortodoxa
de la lista de argumentos. En realidad podramos considerar que minuendo -
sustraendo es equivalente a -(minuendo, sustraendo), y si en esta ltima expresin sustituyramos el operador
de sustraccin por un identificador alfanumrico (del tipo comunmente usado en los lenguajes simblicos como C),
como por ejemplo restaDeFloats, tendramos una funcin monda y lironda. Podemos ver, pues, al operador como
a una triquiuela formalista que nos permite aplicar la notacin algebraica clsica a las expresiones de un
programa (realmente slo a algunas de ellas, como ya veremos), aumentando sobremanera la legibilidad del
cdigo. Hemos visto tambin, despus, que el objeto a la derecha del operador es el que lanza el mensaje,
significado por el operador en s, al objeto situado a la izquierda de ste. Realmente esto es una norma en C++:
el argumento de una funcin situado ms a la derecha en la lista de argumentos lanza el mensaje representado
por la funcin a su elemento contiguo a la izquierda, y as recursivamente. O sea, si tenemos

multiplicando1 x multiplicando2 x
multiplicando3 x multiplicando4

el esquema de envo de mensajes seguira el siguiente orden, establecido


por los parntesis:

( ( ( multiplicando1 x multiplicando2 ) x
multiplicando3 ) x multiplicando4 )

HERENCIA

Resulta innecesario explicar en qu consiste la herencia biolgica (recuerdo


que mi padre me espet una frase similar en mi pubertad). En OOP existe
un concepto parejo, en el que los seres vivos son sustituidos por las
abstracciones. En C++, en concreto, la herencia se aplica sobre las clases. O
sea, de alguna forma las clases pueden tener descendencia, y sta
herederar algunas caractersticas de las clases "padres". Si disponemos las
clases con un formato de arbol genealgico, tenderemos lo que se denomina
una estructura jerarquizada de clases.

Entendmonos: la OOP promueve en gran medida que las relaciones entre


objetos se basen en construcciones jerrquicas. Esto es, las clases pueden
heredar diferencialmente de otras clases (denominadas "superclases")
determinadas caractersticas, mientras que, a la vez, pueden definir las suyas
propias. Tales clases pasan, as, a denominarse "subclases" de aqullas. En
un esquema simplista basado en grafos de rbol, las clases con las
caractersticas ms generales ocuparan la base de la estructura, mientras
que las subclases ms especializadas floreceran en los nodos terminales.
Vemoslo en un simple -y quizs irreal- ejemplo de jerarquizacin:

C++/OOP: UN ENFOQUE PRCTICO Pgina 22/297


Construcciones
Viviendas
Edificios
Apartamentos
Salas de Cine
Casas
Chalets
Monumentos
Contemplativos
Estatuas
El Pensador, de Rodin

Adelantmonos a los acontecimientos: no existe una jerarqua objetivamente


correcta. Los mismos 'n' elementos pueden ser ordenados un mximo de 'n!'
maneras distintas en jerarquas basadas en distintos criterios, ninguno en
principio mejor que otro. Se tiende a pensar, sin embargo, en que el mejor
criterio es el ms natural, el que se ms se acomoda al mundo real. Bueno,
esto es slo una opinin; una muy extendida opinin: lo que se denomina el
criterio antropomrfico. De momento, empero, obviaremos esta discusin
de races filosficas, ms propia de las reas de anlisis y diseo.
Prosigamos.

En C++ la herencia se implementa mediante un mecanismo que se


denomina derivacin de clases: las superclases pasan a llamarse clases
base, mientras que las subclases se constituyen en clases derivadas.

El mecanismo de herencia est fuertemente entroncado con la reutilizacin


del cdigo en OOP. Una clase derivada posibilita, en C++, el fcil uso de
cdigo ya creado en cualquiera de las clases base ya existentes, de la misma
manera que uno de nuestros hijos puede heredar alguna de nuestras
aptitudes (y la prctica totalidad de nuestros defectos). Consideremos el
siguiente ejemplo:

class Edificio {
// ...
protected:
int numeroDePlantas;
char* direccion;
Fecha fechaConstruccion;
// ...
public:
virtual void alarmaEnCasoDeIncendio();
// ...
};

C++/OOP: UN ENFOQUE PRCTICO Pgina 23/297


class SalaDeCine : public Edificio {
// ...
private:
int aforoSala;
// ...
void marchaProyector( boolean );
// ...
public:
void empezarSesion();
void alarmaEnCasoDeIncendio();
// ...
};

Hemos hecho derivar la clase SalaDeCine de la clase base Edificio (en este caso se ha realizado una
derivacin pblica, significada por la notacin : public Edificio de la cabecera de la clase SalaDeCine, y que
explicaremos ms adelante). De este modo aprovechamos la abstraccin realizada en el nuevo tipo de dato
Edificio, no teniendo necesidad de volver a implementar en la clase SalaDeCine las variables y mtodos ya
adscritos a la clase base (efectivamente, la clase derivada puede hacer uso, nicamente restringido por las
etiquetas de acceso, de las funciones y los datos de la clase base sin necesidad de redeclararlos). Esto quiere
decir, en definitiva, que, aunque no lo hayamos declarado expresamente, la clase SalaDeCine encapsula,
tambin, datos como el numeroDePlantas o la fechaConstruccion. Es como si, al derivar la clase SalaDeCine de la
clase Edificio, se aadiera de alguna forma todo lo que hemos declarado en la clase base a la clase derivada. O
sea, que al declarar esta derivacin lo que estamos haciendo es aadir un pegote de la clase "padre" a nuestra
clase "hija". Bueno, la verdad es que fsicamente ocurre ni ms ni menos que esto (con la excepcin de las clases
bases virtuales, de las que hablaremos bastante ms adelante): la porcin del cdigo de la clase base se aade
en el mdulo objeto a la codificacin propia de la clase derivada. Pero, atencin, esto no quiere decir que
podamos usar indiscriminadamente cualquier dato o funcin de la clase base. Vaya! Y por qu no? -preguntar
el lector-: Para qu queremos aadir un "pegote" que luego no podamos utilizar? Bueno, esto tiene que ver con
las "etiquetas" de que hemos hablado antes, y tambin con el tipo de derivacin utilizado. Entonces, hay varios
tipos de derivacin?. S. Existen tres tipos de derivacin: pblica, privada y protegida, como antiguamente
decase que existan tres tipos de descendencia humana: legtima, secreta y bastarda (o natural). Pero, bueno,
contestando a la pregunta podra decirse que el bloque del "padre" se traspasa ntegro y luego, dependiendo del
tipo de derivacin y de las caractersticas de las funciones y datos de la clase base, as como de cmo y quin
pretenda usarlas, el sistema dir: "Puede usarse" o "NO puede usarse". Adems, es bueno que el padre, o clase
base, pueda restringir el acceso a ciertas cosas (pinsese, si no, en el dormitorio conyugal en la vida marital real).

Vemos, no obstante lo anterior, que en ambas clases aparece la misma


funcin miembro alarmaEnCasoDeIncendio(): No se trata de una repeticin, sino que, usando de una
caracterstica que veremos seguidamente -el polimorfismo-, se ha deseado que un objeto SalaDeCine
responda al mensaje de "Alarma!, Fuego!" de una forma distinta a como respondera un edificio de uso general
(de forma comprensible, pues se da una mayor condensacin humana en aquellos locales: esto es, hay que darle
a la carne quemada la importancia que merece). Si no existiera esta "duplicidad" un objeto del tipo SalaDeCine
respondera al mensaje de alarmaEnCasoDeIncendio con el cuerpo de esta funcin definido en la clase Edificio,
segn lo que hemos visto en el prrafo anterior, y quiz no sea esto lo que nos interesa.

El concepto de herencia constituye un estrato bsico del paradigma de


objetos, pero esto no significa que todas las relaciones entre clases en OOP
deban ajustarse siempre a este modelo jerrquico. Es necesario establecer si
la pretendida relacin entre objetos es de pertenencia o de derivacin. En
una relacin tpica de pertenencia un objeto contiene al otro (verbigracia, un
objeto coche posee un objeto motor) mientras que en una de derivacin un tipo de datos abstracto se
extiende constituyendo un subtipo (as se derivara la clase OsoPanda de la clase Oso). Adelantemos que, segn
un esquema ampliamente aceptado, las relaciones entre clases de un sistema podran dividirse en razn de los

C++/OOP: UN ENFOQUE PRCTICO Pgina 24/297


tres siguientes predicados y su ajuste a una determinada conjuncin: ES-UN, TIENE-UN, ES-COMO-UN. Vemoslo
en un ejemplo: un perro es-un mamfero, tiene-una cola y, para los nios, es-como-un juguete. La nica relacin
generadora de un esquema jerrquico es la primera (ES-UN). Esta divisin, que parece una tontera, es
fundamental en la etapa de anlisis y diseo, por lo que el lector debera guardrsela sin ms remora en su
mochila de procedimientos. Debemos acostumbrarnos, pues, a que cuando veamos o distingamos dos objetos nos
preguntemos: qu tipo de relacin los une? ste es un ejercicio muy recomendado que, puesto en prctica,
amortiguar en alguna medida la predisposicin que tienen los principiantes en OOP a establecer cualquier
relacin entre objetos como una jerarqua.

La herencia puede ser simple o mltiple (esta ltima soportada en C++ a


partir de la versin 2.0), dependiendo del nmero de superclases que se
constituyan en base de una dada. La utilidad de la herencia mltiple provoc
en principio una fuerte discusin en la comunidad C++, aunque ya parece
haberse asentado suficientemente su funcionalidad. La herencia puede
establecerse, tambin y como ya hemos visto, como pblica, privada o
protegida (esta ltima a partir de la versin 3.0), dependiendo de la
calificacin del acceso a lo heredado, como ya veremos detalladamente.

En resumen, el principal beneficio de la herencia en un OOPL, y en concreto


en C++, consiste en la fcil reutilizacin de los componentes de un
determinado sistema a travs de la captura explcita de la generalizacin y de
la flexibilidad en la incorporacin de cambios a la estructura. Recordemos:
en OOP pequeos cambios han de generar pequeas repercusiones. O sea,
que podemos aprovechar con poco esfuerzo el cdigo que ya habamos
escrito. Incluso las pruebas de prototipacin.

POLIMORFISMO

Esta propiedad, como su mismo nombre sugiere (slo para los que se
manejen en griego: mltiples formas), se refiere a la posibilidad de acceder
a un variado rango de funciones distintas a travs del mismo interfaz. O sea,
que, en la prctica, un mismo identificador puede tener distintas formas
(distintos cuerpos de funcin, distintos comportamientos) dependiendo, en
general, del contexto en el que se halle inserto.

En C++ el polimorfismo se establece mediante la sobrecarga de identifi-


cadores y operadores, la ligadura dinmica y las funciones virtuales.
Vayamos por partes.

El trmino sobrecarga se refiere al uso del mismo identificador u operador


en distintos contextos y con distintos significados.

La sobrecarga de funciones conduce a que un mismo nombre pueda


representar distintas funciones con distinto tipo y nmero de argumentos.
Esto induce un importante aadido de legibilidad al cdigo. Examinemos por
ejemplo las siguientes declaraciones, supuestas bien definidas las clases
Racional y Real:

C++/OOP: UN ENFOQUE PRCTICO Pgina 25/297


Real& sumaReal( Real, Real );
Racional& sumaRacional( Racional, Racional );

Vemos que funciones con la misma operatividad abstracta -pues en definitiva


se rata de sumar nmeros- observan nombres distintos. Y lo malo es que
esto nos parece de lo ms normal!. En C++ tales funciones podran ser
declaradas as:

Real& suma( Real, Real );


Racional& suma( Racional, Racional );

de forma que el manejo funcional se torna ms intuitivo, facilitando as, a la


vez, el mantenimiento del cdigo. Pero, qu ocurre aqu?. Si codificamos lo
siguiente:

cout << suma( 5, 8 );

cmo sabr el sistema cul de las funciones suma tiene que ejecutar?
Bueno, en sucesivos captulos veremos que el sistema aplica a rajatabla unas
determinadas reglas en un orden muy preciso, y ayudndose de stas
determina (si puede) a qu funcin concreta debe llamar.

En el mbito de la OOP, la sobrecarga de funciones equivale a que un


mismo mensaje puede ser enviado a objetos de diferentes clases de forma
que cada objeto respondera al mensaje apropiadamente: as el mensaje
"suma" dirigido por un objeto Racional a otro obtendra un valor de retorno de tipo Racional (concretamente
referencia a Racional, un tipo introducido por C++ y que veremos ms adelante, y que nosotros muy infor-
malmente podremos, en este momento, asimilar a un puntero constante a Racional con caractersticas sintcticas
especiales), mientras que el mismo mensaje dirigido a un objeto Real provocara el retorno de la referencia a un
objeto tambin Real.

Podemos, pues, dotar de ms significados al nombre de una funcin, tanto


dentro como fuera de la descripcin de una clase. Los programadores de C
pueden inmediatamente sopesar la gran ventaja que esto supone: en lugar
de tener legiones de funciones conceptualmente idnticas y con distintos
identificadores, tales como sumaDeEnteros, sumaDeFloats, sumaDeDoubles,
etc. (que, por cierto, forman una muralla impenetrable para cualquier lector
del cdigo distinto del que lo implement), la funcin encargada de sumar
nmeros ser notada con un nico identificador: suma y, dependiendo del
tipo de los argumentos, el trabajo de discernir a qu funcin concreta se
dirige la llamada es dejado al compilador (vaya! parece que ya empezamos
a ver alguna contraprestacin a nuestra inversin!).

La sobrecarga de operadores permite, por otro lado, el desarrollo de un


cdigo ms coherente, como especializacin de la sobrecarga de funciones,
posibilitando la re-definicin (para tipos de datos definidos-por-el-usuario)
de las operaciones realizadas por stos (+, -, *, >, etc.). Esto es, ocurre lo
mismo que en la sobrecarga de funciones, pero aqu, en vez de
identificadores de funciones, tenemos operadores, que, como ya hemos

C++/OOP: UN ENFOQUE PRCTICO Pgina 26/297


comentado, son en definitiva funciones escritas de una forma
pretendidamente ms clara. De esta manera, el ejemplo anterior podra ser
recodificado de la siguiente forma:

class Racional {
friend Racional& operator+( Racional, Racional );
// ...
};

class Real {
friend Real& operator+( Real, Real );
// ...
};

pudindose usar el nuevo operador como sigue:

Real sumaReal, sumandoReal1, sumandoReal2;


sumaReal = sumandoReal1 + sumandoReal2;

Racional sumaRacional, sumandoRacional1, sumandoRacional2;


sumaRacional = sumandoRacional1 + sumandoRacional2;

donde aparece evidente que los nuevos tipos definidos-por-el-usuario, que


encapsulan las propiedades y atributos de los conjuntos matemticos de los
nmeros racionales y reales, seran tratados por el compilador de igual
forma como ste lo hara con los incorporados.

A estas alturas el lector observar asombrado el cdigo anterior y,


restregndose los ojos, sin duda concluir: diantre!. Unas cuantas lneas con
una extraa sintaxis y terminologa y zas!: sobrecargas, operadores ... qu
pasa aqu? Bien, la verdad es que el cdigo expuesto por s solo no sirve
para nada prctico. En las clases, y dado que todava no hemos llegado al
momento de su explicacin detallada, nicamente estamos significando
"declaraciones" y no "definiciones". Codificamos la declaracinde una funcin
suma u operador '+', pero no decimos nada del cuerpo del mtodo:
tenemos la cajetilla de cartn y nos faltan los cigarrillos de dentro. Pero,
claro, no podemos atacar a la vez todos los flancos. Lo que se est indicando
aqu es que, mediante una propiedad llamada genricamente polimorfismo,
se puede codificar de una forma mucho ms sencilla y legible, mostrando el
aspecto que ofrece en C++.

Es conveniente notar que la sobrecarga de operadores nicamente puede


aplicarse dentro del protocolo de descripcin de una clase o cuando se toma
al menos uno de los argumentos de clase o referencia a una clase. Dicho de
otra forma: no podemos cambiar el modus operandi de los operadores
actuando nicamente sobre tipos pre-definidos. O sea, que si tenemos

int entero1, entero2, entero3;


entero3 = entero1 + entero 2;

C++/OOP: UN ENFOQUE PRCTICO Pgina 27/297


no podemos redefinir aqu el operador '+' para que en vez de sumar
enteros, por ejemplo, los reste. Existe tambin otra restriccin en la
sobrecarga de operadores: no podemos inventarnos los operadores.
Debemos atenernos al conjunto de operadores que ya existe en C++ (que
son los de C ms algn otro especfico como '::' etc.).

Merced a la ligadura dinmica -retomando las propiedades polimrficas


de OOP- pueden invocarse operaciones en objetos obviando el tipo actual
del stos hasta el momento de la ejecucin del cdigo. Los lenguajes
compilados normalmente asocian un llamada a una funcin con una
definicin particular de sta en tiempo de compilacin, posibilitando as la
optimizacin del cdigo y el chequeo de tipos en la misma. Los lenguajes del
tipo de Smalltalk resuelven todas estas llamadas en tiempo de ejecucin,
proporcionando una gran flexibilidad en detrimento de la eficacia del cdigo.
Pensemos, por ejemplo, que deseamos enviar el mensaje "dibjate!" a un
objeto del tipo polgono regular de n-lados y del que ignoramos su subtipo
(pentgono, octgono, etc.) en el momento de la compilacin (como puede
ocurrir en un sistema interactivo de dibujo). Imaginemos que la eleccin del
objeto se realizar por el usuario en tiempo de ejecucin. As, el mismo
mensaje dirigido a un objeto tringulo originar una respuesta distinta del recibido si el objeto
seleccionado es un hexgono. En Smalltalk, por ejemplo, se pueden definir arrays de elementos de distintos
tipos: el compilador no tiene medio de saber el tipo del elemento que ser manejado en tiempo de ejecucin. De
distintos tipos? Entonces no sern arrays! -podra exclamar el lector. Bien, algo de razn hay en esto: dejmoslo
en tipo-agregado-como-un-array.

C++ provee la ligadura dinmica mediante las denominadas funciones


virtuales permitiendo, en virtud de un mecanismo ntimamente relacionado
con la derivacin de clases (y por tanto con la herencia), la redefinicin en
las clases derivadas de funciones miembro declaradas en las clases base, de
forma que ser el sistema, en tiempo de ejecucin (run-time) el que elegir
qu funcin debe ejecutar, dependiendo del objeto al que vaya dirigido el
mensaje. Alto! Alto! Esto no est claro! Veamos: si codificamos un mensaje
dirigido a un cierto objeto, dnde est la duda? dnde la capacidad de
eleccin del sistema? Bueno, retrocedamos un poco. Primero debemos notar
que existe en C++ una forma de codificar un mensaje de manera que pueda
ser dirigido hacia un "conjunto general e indefinido" de objetos, resultando
que el objeto al que al final se aplique el mensaje es desconocido para el
compilador. Volvemos a lo mismo! -exclamar el lector-: si no est mal
entendido, un mensaje equivale, ms o menos, a una funcin, y esta funcin
se aplicar a un objeto concreto: Esto no se entiende! Bueno, vamos a
adelantarnos algo en el programa, aunque sea de forma esquemtica: Si
declaramos una funcin virtual para ser aplicada en un objeto, por ejemplo,
de tipo Edificio, lo que est claro es que si el objeto es de otro tipo, por ejemplo int, se producir un error (ya
en el momento de la compilacin). Sin embargo, si en vez del objeto del tipo Edificio aparece un objeto de una
clase derivada pblicamente de Edificio (como, por ejemplo, SalaDeCine), el sistema buscar una funcin del
mismo nombre y con similar lista de argumentos en la clase derivada y, si existe, ejecutar el cuerpo de sta en
lugar de la que corresponda a la clase Edificio. Hay que tener en cuenta, por otro lado, que estamos hablando de
conceptos bsicos, por lo que se supone que, demonios!, el lector no tiene por qu entenderlo todo. Sigamos.

C++/OOP: UN ENFOQUE PRCTICO Pgina 28/297


Retomemos, una vez ms, el ejemplo flmico mostrando parte de las defini-
ciones de los distintos mtodos de respuesta al mensaje alarmaEnCasoDeIncendio
dependiendo del objeto al que tal mensaje sea dirigido:

Edificio::alarmaEnCasoDeIncendio()
{
virtual llamarEstacionDeBomberos();
virtual llamarEstacionDePolicia();
}

SalaDeCine::alarmaEnCasoDeIncendio()
{
lucesSala( ON );
marchaProyector( OFF );
llamarEstacionDeBomberos();
llamarEstacionDePolicia();
abrirPuertasEmergencia();
// ...
}

Vemos que dependiendo del objeto a que se dirija el mensaje de


alarmaEnCasoDeIncencio la respuesta ser distinta. Pero no queda aqu la cosa. Repitmoslo: el esquema
operativo de las funciones virtuales es el siguiente: supongamos que poseemos un mtodo o funcin que efecta
una llamada a la funcin miembro alarmaEnCasoDeIncencio(). A la funcin miembro de qu clase? Bueno, aqu
esta el quid. Veamos tal llamada como un mensaje dirigido a un cierto objeto, que a la vez es desconocido para el
programador al tiempo de la codificacin. Imaginemos el mensaje como una bala en la escopeta de un cazador al
acecho de cualquier presa de un determinado tipo: cuando aparezca el primer objeto -cuya identidad en principio
desconocemos- la escopeta disparar, el mensaje llegar al objeto y ste responder de una forma apropiada:
si el objeto es un Edificio con el cuerpo de la primera funcin del ejemplo; si es una Sala de Cine, con el cuerpo de
la segunda. Estamos dejando, de nuevo, que sea el sistema el que se ocupe del trabajo sucio, pues pensemos en
la farragosidad de las estructuras de tipo switch o if-else que deberamos trabajarnos a mano. A estas alturas ya
podemos pensar, pues, en el compilador como en un colaborador y no como en El Pozo de Babel.

El lector , llegado a este punto, no debe desesperarse: lo anterior es


nicamente un brevsimo acercamiento a varios conceptos clave.
Posteriormente habremos de ver en detalle -ay!, verdad es que "demasiado"
es siempre poco en C++!- todas y cada una de las caractersticas notadas.
Cabalguemos, pues, sin ms, sobre el siguiente captulo.

C++/OOP: UN ENFOQUE PRCTICO Pgina 29/297


DONDE C Y C++
3
DIFIEREN

Hace poco un amigo me comentaba, jocoso, que C++ ms bien debiera


denominarse '+C+': o sea, 'C rodeado de obstculos, de ms y ms
obstculos'. Bueno, esto es algo exagerado, pero expresa de forma clara el
siguiente esquema: "Al principio crese el lenguaje C y se vio que era
bueno: se poda disfrutar de l en total libertad. Pero el demonio de la
inteligencia tent al hombre con el rbol de la ciencia: C dando ms y ms
frutos, frutos de ms y ms. El programador sucumbi a la tentacin y,
desde entonces, por castigo divino, ya nunca ms se dara la flexible
sencillez del C de antao. Fue dicho entonces al desarrollador: cargars con
la doble cruz del trabajo y del chequeo de tipos: la doble cruz de C++, y
nunca ms trabajars solo." Bueno, slo es una broma (o no?). En
definitiva quiero indicar que, a ojos del observador alejado, C++ es un C
enmaraado y restrictivo. Realmente, por supuesto, esto no es cierto: las
restricciones que C++ impone a la flexibilidad de C, sostenidas mayormente
en el fuerte chequeo de tipos y en la incorporacin de tipos de datos
abstractos (clases), suponen un aumento de la efectividad, claridad y
mantenibilidad del cdigo, reduciendo grandemente los tiempos de
desarrollo y mantenimiento de sistemas software.

C++/OOP: UN ENFOQUE PRCTICO Pgina 30/297


Uno de los puntos fuertes de C++ consiste precisamente en su capacidad
para ser usado e integrado con facilidad de los sistemas basados en C: la
compatibilidad de C++ con C permite, as, aprovechar progresivamente las
nuevas caractersticas del lenguaje, instando la coexistencia inicial de ambos.
Por un lado, y obviando las caractersticas de C++ esenciales a la OOP y a
sus propiedades asociadas, ste puede ser utilizado simplemente como un
mejor C, pudiendo de esta manera los sistemas codificados en C hacer uso
inmediatamente del fuerte chequeo de tipos de C++, as como de la
prototipacin y, en general, de las nuevas caractersticas de uso general que
C++ aade al "sucinto" C. Por otro lado, el conocimiento de las
incompatibilidades entre ambos lenguajes nos permitira evitar en nuestro
cdigo C aquellas construcciones generadoras de problemas en la
compilacin bajo C++. Hay que realizar, pues, en un primer estadio de
transicin desde ANSI C a C++, necesarios ajustes que permitan la compila-
cin sin problemas de nuestro cdigo C como C++.

En definitiva: en las secciones que siguen veremos, esencialmente,


determinadas sintaxis y esquemas de programacin comunes en ANSI C,
pero incompatibles o generadores de problemas en C++. Si conocemos qu
tipo de construcciones C ocasionan problemas al compilar como C++,
estaremos en disposicin de evitarlas y, a la vez, conseguiremos un mejor
cdigo C. Deseo notar que los pequeos ejemplos que se presentan han sido
chequeados con Borland C++ 4.0 para la delimitacin de los errores, pues
ste es, a mi juicio, el compilador para PC's actual que ms se acerca a las
especificaciones de AT&T 3.0 y del ARM. Veamos, sin ms, tales diferencias.

PROTOTIPADO DE FUNCIONES Y CHEQUEO DE TIPOS EN C++

En C++, como en ANSI C, las funciones deben expresamente contener en


su declaracin el nmero y tipo de sus argumentos, constituyendo lo que se
denomina un prototipo de funcin, pero mientras que una lista de
argumentos vaca indica en ANSI C un nmero indeterminado de
argumentos y tipos, la supresin paralela del chequeo de tipos en C++ se
consigue mediante la notacin de elipsis ( ... ). De esta manera cualquier
funcin no declarada previamente, en primer lugar originar cuando menos
un warning, y en el mejor de los casos ser asumida por el compilador C++
como del tipo

int identificadorDeFuncion( ... );

Las siguientes declaraciones en ANSI C

extern sumaDeDosEnteros(); // lista indefinida de argumentos


extern void imprimeElMensajeHola( void ); // lista vaca

en C++ significan ambas: funciones con una lista de argumentos vaca. Si


queremos, sin embargo, expresar en C++ lo mismo que en ANSI C, las
expresiones equivalentes seran:

C++/OOP: UN ENFOQUE PRCTICO Pgina 31/297


extern int sumaDeDosEnteros( ... ); // suprime chequeo de tipo
extern void imprimeElMensajeHola(); // lista de argumentos vaca

pues en C++ una lista de argumentos vaca significa "sin argumentos". Para
asegurar la compatibilidad se aconseja en un primer estadio, aunque en
C++ es un anacronismo, el uso de la notacin funcion(void) para indicar
funciones sin argumentos tanto en C como en C++.

De acuerdo con lo anterior, la funcin main(), en aras de una homogeneidad conceptual, ser
siempre codificada as:

int main( int, char** ) // las funciones siempre deben


{ // declarar sus argumentos
// aqu viene el cuerpo del cdigo
return 0; // la no devolucin de valor en una funcin
// no void causara un warning en compilacin
}

NUEVAS PALABRAS CLAVE

C++ se reserva los siguientes identificadores como parte del lenguaje:

asm catch class delete


friend inline new operator
private protected public template
this try throw virtual
volatile signed overload

El trmino overload ha quedado obsoleto a partir de la versin 2.0 de C++,


aunque se mantiene como parte del lenguaje a efectos de la compatibilidad
con anteriores versiones.

Por tanto si estamos usando alguna de estas palabras clave en un sistema C,


como identificadores de variables o funciones, deberemos renombrarlas para
evitar confictos en tiempo de compilacin en el caso de portar nuestro
cdigo C a C++.

C++/OOP: UN ENFOQUE PRCTICO Pgina 32/297


SALTOS SOBRE DECLARACIONES

Examinemos el siguiente cdigo (si el lector piensa que el ejemplo es


demasiado forzado, slo puedo decir: tiene razn! Aunque yo no me
considero tremendista con respecto al uso de la instruccin goto, lo cierto es
que no se me ocurre una situacin que realmente necesite el tipo de salto
que describimos aqu):

void partidaDeBingoDeSupersticiosos()
{
numero = extraeBola();
if ( numero < 1 || numero > 99 )
goto Fin; //Error en C++
for ( int contador = numero; contador < 99; contador++) {
switch ( contador ) {
case 13:
cout << "La empresa es supersticiosa.";
Fin: cout << "\nFin de partida.";
return;
default:
cout << "Ha salido el nmero: "
<< numero << "\n";
break;
}
}
}

Vemos que una instruccin goto salta condicionalmente al interior del


mbito de un switch contenido en el mbito de un for en el que se ha
inicializado una variable de tipo int (contador), sintaxis sta que por otra
parte resultar extraa al programador de C y que abordaremos ms
adelante en el texto. Esto est prohibido en C++, pues podra dar lugar a la
desinicializacin de una variable no inicializada, al saltar sobre sta (un poco
ms adelante explicaremos el "por qu). Una instruccin goto podra, no
obstante, saltar sobre la totalidad del bloque de mbito en que se declara la
variable. La misma prohibicin es aplicable al salto sobre el constructor de
una clase desde fuera de su mbito. ANSI C no contempla esta restriccin.

Vemos, a la vez, extraas construcciones del tipo cout << X, que responden a la
interpretacin en C++ del grupo printf(...) de C. Digamos, por el momento, que cout es un objeto predefinido
para el direccionamiento de objetos al dispositivo de salida estndar, << es el operador de insercin (recordemos
aqu lo ya dicho sobre los operadores), mientras que X es el objeto a insertar. En definitiva, tal sintaxis resultar
en la impresin del objeto X, tal y como, si de manera informal, escribiramos printf("%x", X). Sigamos.

Bsicamente la prohibicin de salto notada antes adquiere su verdadero


sentido en el salto sobre un mbito en el que se han inicializado constructo-
res de clases. La llamada, explcita o no, un destructor de un objeto cuya
inicializacin ha sido "saltada" en el mismo mbito podra ocasionar
problemas. Constructores? destructores? algn tipo de Terminators? Ok:
est bien: todava no hemos hablado de esto. Adelantar que un constructor
es un cdigo que suple en las clases, en su calidad de tipos definidos-por-

C++/OOP: UN ENFOQUE PRCTICO Pgina 33/297


-el-usuario, los procedimientos de inicializacin con que el compilador
provee a los tipos predefinidos. Pensemos en un array de chars: el
compilador reserva espacio en memoria, asigna un puntero a la primera
celda, etc. Pensemos ahora en un array de Letras, un tipo nuevo (para el
compilador, claro) descrito en una clase Letra codificada por nosotros: se necesitan realizar
parejas maniobras para su inicializacin: aqu entran los constructores. Es fcil imaginarse que funcin tienen los
destructores. Pero entonces, Cmo ...? Vale, vale: ste es un asunto serio: lo aplazaremos hasta verlo en
detalle.

Podemos apreciar el problema en el siguiente ejemplo, en el que un goto


salta por encima de la inicializacin de un objeto de tipo defini-
do-por-el-usuario (Racional), producindose una catstrofe cuando se destruye, al final del mbito, un
objeto que no ha sido inicializado.

void controlaCortoRecorrido( int espacio, int tiempo )


{
if ( !espacio )
goto SinRecorrido; // ERROR en C++
if ( tiempo ) {
// en la lnea siguiente se inicializa
// un nuevo objeto del tipo Racional
Racional* velocidad = new Racional(espacio,tiempo);
cout << "La Velocidad es: " << velocidad;
SinRecorrido: cout << "\n";
// Seguidamente, como fin del mbito de la
// estructura de control if, entrara en accin
// el destructor del objeto "velocidad",
// desinicializndolo.
}
}

Realmente es muy probable que el lector no haya comprendido del todo en


qu se basa esta "prohibicin del salto". Pero lo cierto es que no puedo
explicarlo -por el momento- de un modo mnimamente inteligible sin
extenderme bastante en otros conceptos en los que todava no hemos
entrado y que, en su da, llevarn bastante trabajo para su asimilacin. El
lector deber tomar esta prohibicin, pues, como una orden: "NO USE
GOTO para este tipo de salto". Y yo incluso la simplificara de la siguiente
forma: "EN NINGN CASO USE GOTO". A lo largo de mi vida profesional
he confeccionado, ledo, revisado y estudiado miles y miles de lneas de
cdigo C++ comercial, y puedo afirmar que jams he encontrado una
simple instruccin goto en ellas. Si en C el uso de goto es discutible, en C++
simplemente es innecesario y la mayor parte de las veces perjudicial (vaya!
-dirn ustedes- y eso que deca no ser manitico con respecto a este tema).
Bueno, esto es como lo del sexo: empiezas con casi nada y enseguida te
apasionas.

C++/OOP: UN ENFOQUE PRCTICO Pgina 34/297


INICIALIZACIN DE ARRAYS

Si en la inicializacin de un array se indica su tamao, en C++ ste debe


corresponder exactamente al del inicializador, como vemos en el siguiente
ejemplo autoexplicativo:

// La siguiente lnea causa error en C++, pero es OK en ANSI C


char* aviso[44]="No se ha tenido en cuenta el byte nulo final"
// la siguiente lnea es OK en C++
char* aviso[45]="S se ha tenido en cuenta el byte nulo final"

Dado, por otro lado, que la siguiente definicin en ANSI C

char* holaMundo[4] = "hola"; // Error en C++

es equivalente a

char* holaMundo[] = "hola"; //OK en C++

se recomienda, pues, adoptar esta ltima forma para evitar la incompa-


tibilidad del cdigo, pues con esta ltima sintaxis no tendremos que 'contar'
y evitaremos posibles errores.

CONVERSIN POR ASIGNACIN DE PUNTEROS A VOID

En ANSI C la conversin de un puntero de tipo void* a cualquier otro tipo


se puede realizar de forma implcita mediante una asignacin, mientras que
en C++ esta conversin, como posible generadora de errores muy difciles
de depurar, requiere un cast explcito.

Los punteros a void se suelen declarar en la construccin de componentes


genricos de software, pues pueden ser asignados como punteros a
cualquier otro tipo (incorporado o definido-por-el-usuario).

Examinemos el siguiente cdigo, que compilar correctamente en ANSI C:

// ...
char* dato = "hola"
void* punteroADato = &dato
int* punteroAEntero = punteroADato; // error en C++
// ...
int resultado = 100 * (*punteroAEntero) // INDEFINIDO!
// ...

Hemos convertido, implcitamente, por medio de la asignacin a punteroAEntero,


un puntero a char en un puntero a int, lo que puede producir un resultado errneo o, simplemente, inesperado
(el anterior cdigo compilado con Borland C++ 4.0 en mi PC asigna a resultado el valor 8400). Este cdigo no
compilara en C++, que exigira un cast expreso, como por ejemplo

C++/OOP: UN ENFOQUE PRCTICO Pgina 35/297


int* punteroAEntero = (int*) punteroADato;

asegurando as la intencin del desarrollador en la conversin. O sea, que


forzando al programador a expresar mediante una codificacin expresa su
deseo de realizar esta conversin, el sistema ya puede "lavarse las manos".

La compatibilidad entre C++ y ANSI C impone, pues, la conversin expresa


(cast) del puntero a void* antes de la asignacin a un puntero a otro tipo.

Un problema tpico derivado de esta restriccin sera el ocasionado por el


uso del identificador NULL, definido en muchos archivos de cabecera para uso de ANSI C de la forma

#define NULL (void*)0

as que una asignacin de NULL a un puntero a cualquier tipo, como por ejemplo

// error en C++
int* punteroAInt = NULL;//equivale a punteroAInt = (void*)0

sera flagelada en C++ como error en tiempo de compilacin. Una solucin


a este problema pasara por la inclusin en el archivo de cabecera del
siguiente cdigo:3

#ifdef __cpluscplus
# define NULL 0
#else
# define NULL (void*)0
#endif /*__cplusplus */

Aqu hemos usado de una macro del preprocesador (_cplusplus) que


veremos un poco ms adelante y que, bsicamente, nos permite saber si el
cdigo est siendo compilado como C++ o no.

3
Incidentalmente, y como corolario del espinoso tema de la definicin de NULL,
debemos notar que la portabilidad del cdigo podra verse truncada, tambin, por
declaraciones como la siguiente, encontrada en ficheros de cabecera DOS para el
modelo de memoria large:

#define NULL 0L

pues 0L no puede convertirse en un puntero. Afortunadamente un grupo cada vez


mayor de vendedores de software evita la inclusin directa de cdigo no-portable en
sus productos.

C++/OOP: UN ENFOQUE PRCTICO Pgina 36/297


TIPO DE ENLACE EN VARIABLES CONSTANTES GLOBALES

Las variables globales -declaradas fuera del mbito de cualquier funcin- con
tipo const se consideran en ANSI C con tipo de enlace extern, mientras
que en C++ se enlazaran como static.

Esto quiere decir que si compilamos como C++ un mdulo codificado en


ANSI C conteniendo variables globales constantes, y en otros mdulos se
producen referencias a estas variables, se producir un error en el enlace.

La compatibilidad entre ANSI C y C+ a este respecto puede conseguirse


declarando expresamente como extern tales variables de tipo const:

const Racional mitad( 1, 2 );// enlace static por defecto en C++


extern const Racional tercio( 1, 3 );// fuerza enlace extern

Si seguimos esta indicacin, el cdigo en ANSI C podra ser portado sin


problemas a C++.

ENLACE DE TIPO SEGURO

Resulta que las funciones, funciones-miembro, clases y datos-miembro en


C++ no se codifican en los modulos objeto con la misma simplicidad que en
C. Vaya! Pero, por qu? Cuando antes hemos visitado brevemente el
polimorfismo, hemos visto que, por ejemplo, funciones con el mismo
nombre pueden tener cuerpos distintos. Basndonos en el contexto del
cdigo, el lector podra fcilmente adivinar qu funcin debe ser ejecutada
(dependiendo sobre todo de los argumentos). Sin embargo, si estas
funciones fueran codificadas 'sin ms' en los mdulos objeto, tal y como se
hace en C, tendramos un montn de identificadores repetidos, sin
procedimiento que nos permita elegir adecuadamente. Aqu entra en accin
la codificacin interna, y lo que sta hace es, bsicamente, aadir las
caractersticas del contexto (argumentos, nombre de la clase, etc.) al nombre
de la funcin, o aadir algn smbolo identificativo a ciertas estructuras
especiales como clases y variables como datos-miembro.

Basada en el trabajo del Dr. Stroustrup "Enlace de tipo-seguro en C++", la


codificacin interna (name mangling) de las funciones en C++ en los
mdulos-objeto es realizada aadiendo al nombre del identificador de la
funcin los datos relativos al tipo de sus argumentos y, si es el caso, la clase
a la que pertenecen como miembros. En versiones anteriores a la 2.0 se
produca una deformacin nicamente en la codificacin de las funciones
antecedidas por la palabra clase overload, basada en el orden de
sobrecarga de stas y a fin de evitar la ambigedad en las llamadas a las
mismas. A partir de la versin 2.0, y debido a los fallos de eficacia
observados a este respecto en versiones anteriores, la codificacin "enlace de
tipo-seguro (type-safe linkage)" se produce siempre para todos los identifi-

C++/OOP: UN ENFOQUE PRCTICO Pgina 37/297


cadores de funcin, resultando en una verdadera comprobacin de tipos por
parte del enlazador, por lo que la clave overload ha quedado obsoleta.

Teniendo en cuenta esta caracterstica y si compilamos como C++, toda


funcin C incluida en un programa C++ tambin sera codificada con el
esquema enlace de tipo-seguro, lo que originara un error al intentar
enlazarla con su librera correspondiente, pues el enlazador esperar
encontrar en las libreras la codificacin especial de la funcin, y en stas
solamente encontrara la codificacin interna normal en C. Para evitar este
problema, la supresin de tal codificacin interna debe notificarse expresa-
mente al compilador mediante una directiva de enlace de la forma

extern " LENGUAJE" { /* Codificacin */ }

donde "LENGUAJE" habr de ser sustituido por el cdigo correspondiente


("C++", "C", "Pascal", "Ada", etc.)4.

As, por ejemplo, y como norma general en las libreras y compiladores


comerciales, las funciones de tipo C son codificadas como sigue en sus
respectivos archivos de cabecera:

extern "C" size_t strlen( char* ); // string.h


extern "C" char* itoa( int, int ); // stdlib.h
extern "C" char* gets( char* ); // stdio.h
// ...

o simplemente

extern "C" {
extern size_t strlen( char* );
// ...
}

o an

extern "C" {
# include <stdio.h>
// ...
}

Pero esta codificacin con la sentencia clave extern "C" impedira la


compilacin en C, por lo que, para asegurar la compatibilidad, deben ser
usadas las macros del preprocesador:

4
Hay que notar que el lenguaje C++ nicamente asegura la validez de los
cdigos "C++" y "C", siendo los restantes ("FORTRAN", por ejemplo)
dependientes de la implementacin especfica del compilador.

C++/OOP: UN ENFOQUE PRCTICO Pgina 38/297


#ifdef __cplusplus
extern "C" {
#endif
extern size_t strlen( char* );
extern "C++" peticionSaldo(); /* agujero C++ selectivo en la co-
dificacin de funciones como C */
// ...
#ifdef __cplusplus
}
#endif

No se permite, por otra parte, el uso de tal directiva de enlace dentro del
cuerpo de definicin de una funcin, as como su uso de ninguna manera
implica la conversin de tipos entre lenguajes de programacin (strings en
FORTRAN a strings en C++, etc.), siendo responsable del explcito ajuste el
programador. O sea, que esta directiva, como ya ha sido dicho, nicamente
afecta al name mangling de los identificadores. Nada ms. Y nada menos.
Pensar que una simple clave podra "transformar" cdigo de un lenguaje a
otro, sin ms, es pensar demasiado. Bajemos a planeta Tierra (y digamos,
de paso, que existen herramientas comerciales para llevar a cabo esta tarea,
aunque ste no es nuestro tema).

Es interesante notar que la declaracin extern "C" aplicada a la definicin de una funcin
contenida en un mdulo C++ funcionar correctamente, causando que tal funcin (no sobrecargada, en
previsin de pausibles errores de compilacin) no sea internamente codificada por el esquema de enlace de
tipo-seguro. Tendramos, as, una funcin compilada en C++ con tipo de enlace C. Esta tcnica puede ser
empleada, por ejemplo, para la llamada desde C a funciones miembro de clases en C++.

Retomando el esquema de enlace y de acuerdo con lo anterior, la funcin

int sumaDeEnteros( int, int );

sera codificada internamente en C++, por ejemplo, como sumaDeEnte-


ros__Fii, segn el mtodo propuesto por el Dr. Stroustrup, donde F indica
funcin e ii corresponde a los dos argumentos de tipo int (i). De cualquier
forma, la codificacin interna de nombres depende de la implementacin
C++.

En el caso de Borland, tal y como aparece en su "Manual de Arquitectura


Abierta", tal codificacin interna sigue, bsicamente y en una forma
resumida poco rigurosa, el siguiente esquema:

[@NombreClase][@nombreFuncion$qArgumentos || @datoMiembro]

De esta forma, y con la ayuda de cdigos auxiliares, la misma funcin


anterior sera codificada internamente por Borland C++ como
@sumaDeEnteros$qii. Como podemos apreciar en ambos ejemplos, el
tipo de retorno de la funcin en ningn caso es codificado, lo que ya nos
anticipa que este factor no decidir en la resolucin de sobrecarga de
funciones.

C++/OOP: UN ENFOQUE PRCTICO Pgina 39/297


El uso de depuradores (debuggers) sin mecanismos para decodificar esta
encriptacin de nombres ha causado no pocos quebraderos de cabeza a
muchsimos programadores de C++. Se aconseja, pues, repasar
concienzudamente la documentacin de su implementacin C++.

MBITO DE STRUCT'S

En ANSI C el bloque struct no posee mbito propio, sino que ste es


traspasado al mbito del bloque en el que anida. De esta manera, por
ejemplo, el siguiente fragmento es ilegal en ANSI C:

struct estadoImpresora {
enum { APAGADA, ENCENDIDA, ESPERA } status;
// ...
}
char* ESPERA = "Impresora en espera";

arrojando un error por reutilizacin del identificador ESPERA en el mismo


mbito.

En C++, sin embargo, struct define su propio mbito, por lo que las
enumeraciones declaradas en su bloque son locales a ste. El cdigo
anterior es legal, pues, en C++. O sea que, ojo a lo que contienen los
structs en nuestro cdigo C. Una buena forma de conocer los problemas es
intentar compilar como C++ lo codificado en C, e ir revisando los errores y
warnings uno a uno.

CONSTANTES LITERALES DE CARACTERES

En ANSI C una constante literal de carcter posee tipo int. Esto es,

char letraPorno = 'X';


sizeof( letraPorno ) = sizeof( int ); // Ok en ANSI C

En C++, sin embargo, una constante literal de carcter posee tipo char:

sizeof( letraPorno ) = sizeof( char );

de forma que en C++ no es necesariamente cierto 5 que

5
Cuando decimos que algo no es necesariamente cierto en C++, esto significa
que ese "algo" en cuestin depende de la implementacin C++. Es decir, en un
sistema dado un char puede ocupar los mismos bits que un int, mientras que en
otro estos dos tipos pueden tener tamaos distintos. La estandarizacin de C++
nicamente impone que un determinado tipo debe tener un tamao igual o mayor

C++/OOP: UN ENFOQUE PRCTICO Pgina 40/297


sizeof( char ) = sizeof( int ); // Ok en ANSI C

Esta caracterstica de C++ ha sido implementada para evitar ambigedades


en la sobrecarga de funciones, como veremos ms adelante.

TIPO DE LOS ENUMERADORES

Un enumerador posee tipo int en ANSI C, mientras que posee el tipo de su


enumeracin en C++. Esto es, en el siguiente cdigo

enum boolean { FALSO, VERDAD };


boolean soyGuapo, tengoDinero;

las variables soyGuapo y tengoDinero seran de tipo int en ANSI C, mientras que en C++ seran de tipo
boolean.

De esta forma la expresin

sizeof( soyGuapo ) = sizeof ( int );

que es siempre cierta en ANSI C, no lo es necesariamente en C++.

MACRO __cplusplus

Si se desea construir un cdigo que compile indistintamente en C y C++,


mezclando ambos estilos y declaraciones, debe usarse la macro
__cplusplus junto con las directivas condicionales del preprocesador
#ifdef e #ifndef. Por ejemplo

#ifdef __cplusplus
const limiteVector = 10;
#endif /* __cplusplus */
#ifndef __cplusplus
#define limiteVector 10
#endif /* __cplusplus */

Se pueden aprovechar las caractersticas de macro-expansin de las


directivas del preprocesador para delimitar perfectamente las particula-
ridades de C++ y C, de forma parecida a como se mostr en la descripcin
del enlace tipo-seguro expuesta anteriormente.

que otro, pero no impone condiciones y deja libertad en este sentido a la


implementacin de C++.

C++/OOP: UN ENFOQUE PRCTICO Pgina 41/297


De esta manera, si el cdigo se est compilando como C++, _cplusplus se
asimilar como definido y se compilar, en el ejemplo anterior la lnea que
empieza con const. En otro caso, se compilar la otra sentencia.

DEFINICIONES MLTIPLES

En tanto que en C++ una declaracin de variable sin el cualificador extern


ser asumida como una definicin -dejando al desarrollador la
responsabilidad de diferenciar entre declaracin y definicin-, en ANSI C se
permiten las declaraciones mltiples de variables globales, considerndolas
"definiciones tentativas" y promoviendo a definicin nicamente a una de
ellas. Por ejemplo:

char* cineParadox;
char* cineParadox; //error en C++: mltiple inicializacin
// ...
int main( int, char** )
{
char* cinePalafox;
char* cinePalafox; // error en ANSI C y en C++
// ...
return 0;
}

Las declaraciones de variables sin el cualificador extern son consideradas


como definiciones en C++ debido fundamentalmente a una cuestin de
compatibilidad y homogeinizacin conceptual de los tipos incorporados con
respecto a los tipos definidos-por-el-usuario (clases). Implementemos, por
ejemplo, una clase que encapsule las caractersticas del tipo de dato float:

class Float {
private:
float numero;
// ...
};

La sentencia

Float numeroConComaFlotante;

corresponde a una definicin en ambos, C y C++, mientras que

float numeroConComaFlotante;

es considerada como una declaracin en ANSI C y como una definicin en


C++. Esta inconsistencia conceptual es la que se ha pretendido evitar en
C++, por lo que una declaracin en este lenguaje de, por ejemplo, un int
habra de realizarse de la forma:

C++/OOP: UN ENFOQUE PRCTICO Pgina 42/297


extern int declaracionDeInt;

El programador de C no acostumbra a separar de forma clara las


definiciones de las declaraciones, por lo que lo anterior le puede parece, al
menos, confuso. En C++ pueden existir mltiples declaraciones, pero slo
una definicin. Ante la duda de si una expresin constituye una definicin o
una declaracin, lo ms expeditivo es aplicar la palabra clave extern. En el
"Libro de Respuestas" de Tony Hansen pueden encontrarse multitud de
ejemplos y ejercicios sobre ste y otros temas.

ESPACIO NICO DE NOMBRES DE IDENTIFICADORES Y


ESTRUCTURAS

Las estructuras class, struct y union comparten en C++ el espacio de


nombre con el resto de los identificadores, de forma que el nombre de una
estructura local puede ocultar la visibilidad de un identificador de mbito
exterior a la estructura. Por ejemplo,

int direccion = 1;
void introduceDatosClientes() {
struct direccion {
char* tipoViaPublica;
char* calle;
int numeroDePolicia;
char* codigoPostal;
char* ciudad;
char* provincia;
};
// la siguiente sentencia imprimir el nmero 1
// en el dispositivo de salida estndar
// si este cdigo se compila en ANSI C,
// procurando, sin embargo, un error en C++
// por uso impropio del identificador
printf( "%i\n", direccion + 0 ); //error en C++
// la siguiente sentencia equivale, en ANSI C, a
// sizeof( int )
printf( "%i", sizeof( direccion ) );
// ....
}

La compatibilidad puede salvarse con el uso explcito antes del nombre de


uno de los cualificadores enum, class, struct, union o ::. De esta manera
las ltimas lneas del cdigo anterior podran ser reescritas de la siguiente
forma:

cout << ::direccion + 0; // Ok en C++


cout << sizeof( struct direccion );

C++/OOP: UN ENFOQUE PRCTICO Pgina 43/297


Aqu hemos usado el operador cualificador de mbito (::) para adscribir el
identificador usado al correspondiente a la variable global, como veremos en
sucesivos captulos.

Esta restriccin ha sido impuesta en C++ para permitir el uso de la sintaxis


para constructores de clases:

class SalaDeCine {
// ...
public:
SalaDeCine() {};
SalaDeCine( char* );
// ...
};
SalaDeCine cineParadox = SalaDeCine( "Cine Paradox" );

C++/OOP: UN ENFOQUE PRCTICO Pgina 44/297


DONDE C++
4
SE DESTAPA

Dnde nos habamos quedado? Bien, en el captulo anterior pudimos


repasar brevemente los cambios que han de aplicarse a nuestro cdigo ANSI
C para que pueda compilar sin mayores problemas como C++. O sea, el
primer paso en un camino que oportunamente iremos sembrando de una
suerte de migas de pan (los "objetos" de Pulgarcito), de forma que podamos
en cualquier momento desandar lo andado y mirar de nuevo el mapa que
nos conducir al final de esta serie. No nos perdamos, pues. Bien, en esta
ocasin vamos a atacar las caractersticas de C++ que suponen una mejora
sobre C, pero sin entrar todava en el nucleo del nuevo lenguaje: la clase.
Esto es, vamos a fijar nuestra atencin en los posibilidades de C++ no
entroncadas directamente con la OOP, de forma que podran ser incluidas
incrementalmente en nuestro cdigo C. Estas caractersticas, que suponen
mejoras efectivas sobre C, resultan en un aumento real de la eficacia de este
lenguaje, a la vez que proporcionan un soporte ms adecuado para el
desarrollo de aplicaciones. Pero antes de seguir, y como ya es costumbre,
una nota al lector avisado: rigor, brevedad, C++, introduccin y claridad son
asuntos de difcil conjuncin (que no imposible), as que paciencia!. El
apartado dedicado a los operadores new y delete, por ejemplo, rene y
explica algunos aspectos difciles de encontrar juntos en otras fuentes, con la
posible desventaja de tener que involucrar conceptos sobre clases que slo
se comprendern efectivamente ms adelante. Pero, bueno, ya sabemos que
en C++ debemos acostumbrarnos a oscilar adelante y atrs, ms y ms, en
nuestras lecturas y en nuestro cdigo. La parte dedicada a la sobrecarga de
funciones, sin embargo, no podr ser todo lo extensa que yo quisiera: aqu
no se trata de confeccionar un manual de referencia ni una descripcin
completa del lenguaje, de forma que no podemos pararnos en "el
planteamiento del problema del anlisis preparatorio del C++",
parafraseando a Heidegger. Un poco de aqu y un poco de all: algo sobre lo
que el lector pueda basarse en su primer acercamiento al lenguaje.
Empecemos sin ms dilacin.

C++/OOP: UN ENFOQUE PRCTICO Pgina 45/297


DELIMITADORES DE COMENTARIO

Bueno, esto ha llegado a convertirse en una suerte de norma: cuando se


crea un nuevo lenguaje parece casi obligada la creacin de unos particulares
delimitadores de comentarios ('%' para Turbo Prolog, '*' en Fortran, 'rem' en
el antiguo Basic, etc.), como si el propio lenguaje no fuera ya
suficientemente diferente. Bien: C++ no poda ser menos. En principio, y en
aras de esa siempre buscada compatibilidad, C++ admite los delimitadores
de comentario usados en C, de forma que, como en este lenguaje, el
preprocesador sustituir por un espacio el bloque empezando en '/*' hasta
encontrar '*/'. Por ejemplo:

/*
Los delimitadores de este comentario
corresponden al viejo estilo C.
*/

Estos delimitadores, como es bien sabido, son usados para encerrar varias
lneas de cdigo y no se pueden anidar: esto es, el cdigo

f = m * a /* m /* masa */ * a /* aceleracin */ */;

resultar en el siguiente error

f = m * a * a */;

An ms frecuentemente se da el error de la siguiente forma, normalmente


producido al desechar parte del cdigo ya escrito con sus propios
comentarios:

/*
void solicitaClaveDePaso()
{
cout << "Introduzca clave de acceso: ";
cin >> clave; /* mi clave: LOGOS */
validaClave( clave );
}
*/

En este caso resultara el error

validaClave( clave ); } */

Algunos compiladores comerciales permiten el anidamiento de comentarios,


lo que permitira preprocesar sin problemas el anterior ejemplo, pero esta
prctica debe evitarse en aras de la portabilidad del cdigo.

Seguidamente, como ya habamos avisado y el lector ha podido apreciar en


los captulos precedentes, una vez aceptado el viejo estilo de comentarios en
C, C++ introduce un nuevo delimitador: '//', que fuerza al compilador a

C++/OOP: UN ENFOQUE PRCTICO Pgina 46/297


ignorar todo cuanto se halle incluido desde el mismo delimitador hasta el
final de la lnea.

Debe notarse que aunque en C y C++ los espacios no suelen ser


significantes, si se introduce uno o ms entre los dos caracteres de
cualquiera de los delimitadores notados, stos dejarn de constituirse en
tales: o sea, los delimitadores de comentarios en C++ son grupos de dos
caracteres unidos sin espacios. De acuerdo con esto, las siguientes lneas,
por ejemplo, procuraran sendos mensajes de error:

f = m * a; / * frmula de la fuerza * /
v = e / t; / / frmula de la velocidad

Existen, por otro lado, situaciones curiosas, sealadas en la mayora de los


manuales elementales sobre C++, como la siguiente:

double v = e //* dividido por tiempo */ t;


;

donde si compilamos como C++ resulta

double v = e;

mientras que al compilar como C aparece

double v = e/t;

Este problema puede evitarse insertando un espacio tras el operador '/' as:

double v = e / /* dividido por tiempo */ t;

En general un comentario puede insertarse en cualquier lugar del cdigo


dondequiera que tambin sean legales un espacio, una tabulacin o un
cdigo de final de lnea.

Por ltimo, una advertencia: debe evitarse el uso de comentarios de la forma


'//' en las macros del preprocesador, pues la expansin de stas podra
provocar "problemas". El siguiente cdigo, por ejemplo,

#define HABITACIONES 7 // vaya palacio!


// ...
int superficieHabitable[ HABITACIONES ];

producir tras la macro-expansin la lnea

int superficieHabitable[ 7 // vaya palacio! ];

resultando en el cdigo errneo

int superficieHabitable[ 7

C++/OOP: UN ENFOQUE PRCTICO Pgina 47/297


Esto puede evitarse usando el viejo estilo de comentarios de C con las
directivas del preprocesador, como por ejemplo

#define 2PI 6,28306 /* doble de PI */

Bien, queda por considerar la cuestin: cundo se debe usar uno u otro
delimitador? En teora el estilo C debera utilizarse para encerrar bloques de
varias lneas, mientras que los nuevos delimitadores parecen ms apropiados
para comentarios puntuales de lneas concretas de cdigo. Qu ocurre, sin
embargo, en el planeta Tierra? -como dira Allen-. Lo cierto es que si
examinamos un archivo de cabecera tpico de C++ encontraremos una
letana interminable de '//' situadas al inicio de cada lnea, donde pudiera
parecer mucho ms apropiado y requerira muchas menos pulsaciones el
viejo estilo C: o sea, en el fondo a los desarrolladores de C++, como
comentbamos al principio, nos gusta diferenciarnos y, demonios!, por qu
no? Para el principiante puede significar tambin un aliciente pensar que, al
comentar de esta nueva forma su primera lnea de cdigo, ya est
"desarrollando en C++". Bendita salvedad!

OPERADOR CUALIFICADOR DE MBITO (::)

El operador '::' posee dos usos en C++. Por un lado puede utilizarse para
acceder a una variable global cuya visibilidad ha sido ocultada por una
variable local. Vemoslo en el siguiente ejemplo:

// ...
// la siguiente funcin fue definida anteriormente
int edad = calculaEdad( fechaNacimiento);
const int edadMinima = 18; // siempre mejor que usar #define
void intentaFranquearAccesoABar( int edad )
{
// slo modificar la variable local edad
if ( ::edad < edadMinima ) // chequea la variable global
edad = 16; // cambia el valor de la variable local
cout << "Djeme pasar al bar: ya tengo "
<< edad << " aos.";
// ::edad no ha cambiado en ningn caso
}

Bueno, el ejemplo est un poco "trado por los pelos", pero nos permite
recordar que debe tenerse en cuenta que el mbito del parmetro formal de
una funcin se limita al mbito local delimitado por el bloque constituido por
sta. La notacin ::edad se refiere a la variable global edad de tipo
constante int, mientras que la variable local edad contenida en el mbito
local de la funcin oculta la visibilidad de aqulla.

En el ejemplo que sigue, bastante ms didctico, podemos observar cmo el


cualificador de mbito slo permite el acceso a la variable global, imposibili-

C++/OOP: UN ENFOQUE PRCTICO Pgina 48/297


tando el acceso a variables con la visibilidad solapada pertenecientes a
mbitos intermedios. Vemoslo en la prctica:

float i = 13.13; // variable global


void limitacionAccesoAVariable()
{
char i = '?'; // inaccesible desde el siguiente 'for'
for ( ;; ) { // 'forever'
int i = 0; // variable local al bucle 'for'
// desde este bloque local no se puede acceder a la
// variable local 'i' de tipo 'char'
// del bloque local intermedio.
cout << "Variable local de tipo int: "
<< i++ << "\n";
cout <<"Variable global de tipo float"
<< ::i << "\n\n";
if ( i > limiteIteraciones )
break;
}
}

Y esto no cambiara por el hecho de que en este caso no existiera variable


global. O sea, el operador '::', por expresarlo grficamente, practica un
agujero en el bloque o estructura local a travs del cual "trae" a ste una
variable global: nada ms (y nada menos). Esto nos proporciona un nivel
adicional de flexibilidad en la no siempre fcil eleccin de los identificadores.

El operador '::' se usa tambin en C++ cuando una funcin miembro de una
clase se define fuera del bloque en que sta se declar. Bueno, ya sali: ni
habindolo anunciado en el prembulo podemos libranos de las clases: al fin
y al cabo estamos en C++. Intentar explicarlo mediante un ejemplo:

class Racional {
private: // etiqueta cualificadora de acceso privado
int numerador; // dato miembro de la clase
// ...
public: // etiqueta cualificadora de acceso pblico
// sigue una declaracin de una funcin miembro
void estableceNumerador( int );
// ...
};

void Racional::estableceNumerador( int numero )


{ // definicin funcin miembro
numerador = numero;
}

Lo que en realidad ocurre es que el operador '::' cualifica a las funciones


miembro a las que se aplica, permitindoles el acceso al protocolo de
descripcin de sus respectivas clases. De hecho, y siguiendo con la sintaxis
"grfica" expuesta poco antes, podramos decir que la construccin con
sintaxis 'nombreDeClase::' produce un "agujero" a travs del que se trae una

C++/OOP: UN ENFOQUE PRCTICO Pgina 49/297


porcin del mbito de la descripcin de la clase a la funcin que estamos
definiendo, de forma que en realidad la estamos definiendo "virtualmente
dentro" de la clase.

Si se ha entendido bien el funcionamiento del operador, el lector podr


ahora apreciar que lo que al principio se han establecido como dos usos
distintos de ste, se corresponden a una misma accin, pero con distinta
sintaxis. Usando ahora un smil parejo al expuesto anteriormente, podramos
decir que el operador '::' coloca lo que tiene a su derecha en el mbito de lo
que aparece a su izquierda (una clase o "nada" en el caso del mbito global).

DECLARACIONES MEZCLADAS CON EXPRESIONES

Quin no ha observado en un sistema codificado en C una enorme


acumulacin de declaraciones de variables situada bien al principio del
cdigo bien inmediatamente de los bloques en que stas seran definidas?
No se constituye, por otro lado, en motivo de orgullo de muchos
programadores en C -y en otros lenguajes "estructurados"- entre declara-
ciones y definiciones de variables? Quin no se ha perdido alguna vez en el
largo y complejo cdigo de una funcin intentando seguir la pista a
identificadores de seales e iteraciones?

Una codificacin tpica de C podra ser la siguiente:

long fila, columna;


/* algunas lneas de cdigo */
for ( fila = 0; fila < maximoFilas; fila++ )
for ( columna = 0; columna < maximoColumnas; columna++ )
cout << matriz[ fila ][ columna ];

En C++, empero, se relaja al lmite la obligacin que C impone sobre la


precedencia de las declaraciones sobre variables antes de su uso en el
cuerpo de una funcin o en el mbito global de un mdulo. De esta forma
C++ permite la mezcla sin restricciones de las declaraciones con las
expresiones, resultando que el anterior cdigo podra ser reescrito en C++
as:

for ( long fila = 1; fila < maximoFilas; fila++ )


for ( long columna = 0;
columna < maximoColumnas; columna++ )
cout << matriz[ fila ][ columna ];

Hay que notar que el mbito de una variable declarada dentro de una
expresin no se limita al bloque que sta define, sino que se extiende desde
el lugar de declaracin hasta el final del bloque que contiene a la expresin.
Vemoslo en el siguiente fragmento de cdigo:

for ( long permutacion = 1, iterador = numeroDeElementos;

C++/OOP: UN ENFOQUE PRCTICO Pgina 50/297


iterador > 0;
permutacion *= iterador-- );
cout << "Permutacin de " << numeroDeElementos
<< " elementos: " << permutacion; // OK en C++

// la siguiente declaracin ocasionara un error en C++


// debido a la reutilizacin de un identificador: iterador,
// ya definido como int
char* iterador = "Operador de la accin de iterar";
// Se causa, as, un error en C++ por mltiple declaracin

O sea, que en realidad es como si las variables permutacion e iterador se


declararan, como es usual en C, justo antes del bloque 'for'. Y efectivamente
as es: lo que C++ nos permite es clarificar la notacin para permitir una
ms fcil lectura del cdigo, pero sin olvidar la raz prctica del asunto.

VALORES POR DEFECTO EN PARMETROS FORMALES

Todos o algunos de los parmetros formales de una funcin se pueden


declarar por defecto. Esto es, en el momento de declaracin de la funcin,
en su definicin o en ambas (si coinciden) se especificarn los valores por
defecto que asumirn los parmetros deseados, de forma que cuando se
produzca una llamada a la funcin sin especificar tales parmetros sern
asumidos los notados en la declaracin. Por ejemplo, la funcin

void dibujaVentana( int x = 1, int y = 1,


int anchura = 20, int altura = 10 );

pretende dibujar una ventana en la pantalla del computador. Si no se dan


argumentos en la llamada a la funcin, dibujar una ventana con los
argumentos por defecto. Examinemos algunos ejemplos:

// la siguiente lnea equivale a dibujaVentana( 1, 1, 20, 10)


dibujaVentana();
// la siguiente lnea equivale a dibujaVentana( 2, 3, 20, 10 )
dibujaVentana( 2, 3 );
// la siguiente lnea equivale a dibujaVentana( 5, 2, 12, 12 )
dibujaVentana( 5, 2, 12, 12 );

Si se declaran valores por defecto slo en algunos de los parmetros


formales, stos deben significarse consecutivamente sin interrupciones hasta
el final de la lista de argumentos. Por ejemplo, otra funcin similar a la
anterior podra ser declarada como

void dibujaRectangulo( int x, int y,


anchura = 20, altura = 10 );

y las llamadas a sta podran ser

C++/OOP: UN ENFOQUE PRCTICO Pgina 51/297


dibujaRectangulo( 4, 2 ); // ( 4, 2, 20, 10 )
dibujaRectangulo( 6, 3, 17 ); // ( 6, 3, 17, 10 )

Hay que notar que si la declaracin y la definicin de la funcin no son


coincidentes, la asignacin de los parmetros por defecto nicamente habr
de ser significada una vez: bien en la declaracin (o declaraciones) bien en la
definicin, originndose un error en caso contrario por redeclaracin de tales
parmetros. Esto es, pueden darse los siguientes casos:

// variables globales
const float proporcion = 1;
int coordenadaX = 20;
// parmetros por defecto en declaracin funcin
double transformacionEscala( double, double = 1.0 );
// sigue declaracin "normal"
void solicitaConfirmacion( int, int );
// ...
double transformacionEscala( double parametro, double escala )
{
return parametro * escala; // escala vale 1.0 por
// defecto
// (en declaracin funcin)
}
// parmetros por defecto en definicin
// y declaracin coincidentes
float escalaProporcion( float medidaReal, float medidaPlano,
float coeficiente = proporcion )
{
return coeficiente * medidaReal / medidaPlano;
}
// parmetros por defecto en definicin funcin
void solicitaConfirmacion( int x = coordenadaX, int y = 40 );
{
CajaSiNo::dialogo( this, x, y ); // 'this' es un
// puntero implcito
// al objeto
}

En este ejemplo hemos utilizado variables globales (constantes o no) como


argumentos por defecto de dos funciones. El uso, empero, de variables
locales hubiera resultado en un error al compilar, como en el siguiente
ejemplo:

void imprimeNodos()
{
int miOrden = 1;
// la siguiente definicin de funcin incurre en ERROR
// en C++ 3.0, compilando, sin embargo, correctamente
// bajo C++ 2.0 y anteriores.
void recorreArbolBinario( Arbol* miArbol,
orden = miOrden );
}

C++/OOP: UN ENFOQUE PRCTICO Pgina 52/297


aunque sin duda quedar ms claro (o, al menos, eso espero) en el siguiente
ejemplo completo:

#include <iostream.h> /* para las operaciones de


entrada y salida en C++ */

int main( int,char** )


{
int local=5; // variable local (no-global)
void foo( int, int, int=3, int=4 );
void foo( int, int=2, int, int); //redeclaracin OK
void foo( int=local, int, int, int);// error en C++ 3.0

foo( 1 ); // imprimira '1234'


foo(9,9); // imprimira '9934'
foo(9,9,9,9); // imprimira '9999'

return 0;
}

void foo( int w, int x, int y, int z )// definicin de funcin


{
cout << w << x << y << z << "\n";
}

Tal restriccin parece lgica si consideramos el propsito primario de la


inclusin de parmetros por defecto como caracterstica del lenguaje:
permitir una mayor claridad del cdigo, aislando los datos de frecuente uso
(en una operacin que recuerda que recuerda a la contraccin de
expresiones matemticas mediante la aplicacin del factor comn) y
permitiendo su agrupacin en mdulos nicos fciles de revisar y mantener.
El uso de variables locales truncara sobremanera tal disposicin. De
cualquier forma -y sobre todo para aquellos lectores con dificultades para
aislar los conceptos de "declaracin" y "definicin"- debo notar que en el
ejemplo anterior he vuelto a caer en la tentacin (como "reincidente
autotentativo" podra ser calificado por Tweedledum y Tweedledee) de
exponer un aspecto an no visto sobre la redeclaracin "correcta" de una
funcin asignndole nuevos parmetros por defecto. Bueno, la verdad es
que marcar el ritmo del relato es, lamentablemente, una prerrogativa ma
frente a la paciencia del hirsuto lector. Pero, vaya, sin ms dilacin
examinemos esta caracterstica.

Observamos, pues, que una funcin dada podr ser redeclarada para
aadirle parmetros por defecto, con las nicas restricciones de no incurrir
en la redeclaracin de asignaciones de valores por defecto y no vulnerar la
continuidad de tales asignaciones desde su aplicacin en un parmetro hasta
el parmetro final de la funcin. Veamos algunos ejemplos, comentando
bajo cada lnea su correctitud y, en su caso, un ejemplo de aplicacin:

C++/OOP: UN ENFOQUE PRCTICO Pgina 53/297


void dibujaPunto ( int x, int y );
void distanciaAOrigen ( int x, int y, int z );
void dibujaPunto ( int x, int y = 40 );
// OK: ( 1 ) ==> ( 1, 40 )
void dibujaPunto (int x, int y = 20 );
// ERROR
void dibujaPunto ( int x = 20, int y = 40 );
// ERROR
void dibujaPunto ( int x = 20, int y );
// OK: () ==> ( 20, 40 )
void distanciaAOrigen ( int x = 5, int y, int z );
// ERROR
void distanciaAOrigen ( int x, int y, int z = 10 );
// OK: ( 6, 12 ) ==> ( 6, 12, 10 )
void distanciaAOrigen( int x, int y = 20, int z = 10 );
// ERROR
void distanciaAOrigen ( int x, int y = 20, int z );
// OK: ( 7 ) ==> ( 7, 20, 10 )
void distanciaAOrigen ( int x = 5, int y, int z );
// OK: () ==> ( 5, 20, 10 )

Ntese que no se trata aqu de la sobrecarga de la funcin dada, pues no se


producen cambios en la signatura, identificador ni valor de retorno:
nicamente se le aade una facilidad de uso. Note el lector, tambin, que
puede encontrarse con una sorpresa si intenta chequear el anterior cdigo en
su compilador. Realmente si compila el ejemplo en Borland C++ 3.1 todas
las lneas a partir de la cuarta sern flageladas como errores. Vaya! y por
qu? -preguntar el sufrido lector. Bien, lo que ocurre es que, por ejemplo,
al compilar la quinta lnea, Borland C++ 3.1 arroja un error por
redeclaracin del valor del argumento por defecto para el parmetro 'y', pero
sin embargo aade el valor por defecto 'x=20' al parmetro 'x' de la funcin,
con lo que la siguiente lnea originar un error por la redeclaracin del valor
del argumento por defecto para el parmetro 'x'. O sea, que el compilador
asumir parcialmente lo expresado en la lnea a considerar, rechazando
nicamente lo que califique como errneo. Solucin? El lector puede ir
cancelando con '//', de arriba hacia abajo, las lneas que originen error hasta
que llegue a una correcta, para inmediatamente despus volver a compilar, y
as sucesivamente. Como es evidente, esto no es una caracterstica del
lenguaje.

Pero, bueno, en la prctica diaria de programacin, para qu sirve lo


expuesto? Bien: la aplicacin de tales redeclaraciones aparece evidente en la
personalizacin de algunos mdulos. Pensemos, por ejemplo, que deseamos
utililzar una funcin de una librera comercial que dibuje una ventana tpica
"Acerca de ..." declarada as:

extern void ventanaAcercaDe( Ventana*, String, int x, int y );

y de la que no poseyramos el cdigo fuente, con lo que no podramos de


forma expedita adecuarla a nuestros gustos. Sin embargo, y en base a lo
expuesto, si deseramos aplicar unas coordenadas fijas para tales ventanas

C++/OOP: UN ENFOQUE PRCTICO Pgina 54/297


en nuestra aplicacin, podramos redeclarar la funcin, en aras de la
simplificacin del cdigo, de la siguiente forma:

extern void ventanaAcercaDe( Ventana*, String,


int x = 40, int y =20 );
// a partir de aqu el uso es transparente para el desarrollador
ventanaAcercaDe( this*, "Mi Ventana" )

de manera que no tendramos que teclear repetidamente tales valores en


nuestro cdigo. La escritura y lectura del programa quedara, as,
grandemente simplificada.

La declaracin de valores por defecto en parmetros formales puede llevar,


sin embargo, a situaciones de ambigedad con la sobrecarga de funciones,
como podremos ver ms adelante.

REFERENCIAS Y PASO DE PARMETROS POR REFERENCIA

En C++ se denomina referencia a un tipo derivado obtenido aadiendo el


sufijo & a un tipo dado. As, por ejemplo, float& se lee: "referencia a float".

Una variable por referencia siempre debe ser inicializada en el acto de su


declaracin, convirtindose en tal momento en un alias de su inicializador.
No es legal, verbigracia, el cdigo

double& referenciaANada; // error: variable sin inicializar

Las referencias se comportan como variables normales del tipo al que


referencian, con la particularidad de estar ligadas a la direccin de memoria
de su inicializador. Esto es, de alguna forma se comportan como punteros,
pero con la peculiaridad que no tienen que desreferenciarse para ser accedi-
das. Vemoslo en el siguiente ejemplo:

int original = 13;


int& referencia = original;
referencia++;
cout << original << "\n"; // salida: 14
original--;
cout << referencia << "\n"; // salida: 13

Como vemos, si autoincrementamos la referencia, se incrementa el original,


y si autodecrementamos ste, se minora la referencia: naturalmente!, pues
ambos identificadores estn ligados a la misma direccin de memoria.

En C++ no estn permitidas las referencias a referencias, los arrays de


referencias, los punteros a referencias o las referencias a void.

C++/OOP: UN ENFOQUE PRCTICO Pgina 55/297


Si una referencia se inicializa con una constante, como quiera que no puede
afectarse a una direccin de memoria, el compilador crea un tipo temporal.
O sea:

int& numeroCabalistico = 7;

es tratado por el compilador de la forma

int enteroTemporal = 7;
int& numeroCabalistico = enteroTemporal;

As mismo, la asignacin de un inicializador con tipo distinto al apuntado por


la referencia genera, tambin, una variable temporal. Vemos, pues, que

float unidad = 1.0;


int& floatTruncado = raizDeDos;

equivale en la prctica a

int enteroTemporal = int( unidad ); // cast funcional


int& floatTruncado = enteroTemporal;

Las referencias son usadas como parmetros formales de funciones por las
mismas razones que se usan los punteros: para permitir la modificacin de
los datos en el mbito local de las funciones y para evitar la penalizacin en
tiempo de ejecucin que supone la copia de los argumentos en las llamadas
a funciones con argumentos pasados por valor. Examinemos el siguiente
ejemplo:

void intercambiaDatos( int& primero, int& segundo )


{
int temporal = segundo;
segundo = primero;
primero = temporal;
}
// ...
int primerParametro = 1;
int segundoParametro = 2;
intercambiaDatos( primerParametro, segundoParametro );
cout << primerParametro; // salida: 2
cout << segundoParametro; // salida: 1

Vemos que, en comparacin con la sintaxis de punteros, el cdigo local a la


funcin del ejemplo es ms natural e intuitivo, al no necesitar operadores de
desreferenciacin. Advertimos, tambin, que aunque la funcin se declar
con dos parmetros "referencias a entero", en la llamada a sta se aplican
variables de tipo 'int' (y no int&, producindose una conversin trivial), de
forma que se produce un "paso" de tales variables "por referencia" de una
forma similar a la conocida por los lectores con experiencia en Pascal.

C++/OOP: UN ENFOQUE PRCTICO Pgina 56/297


Aparte de todo lo anterior, las referencias son insustituibles en la
inicializacin por asignacin de constructores de clases, como ya veremos,
en ese continuo "huir hacia adelante" en que se est convirtiendo esta serie.

EL ESPECIFICADOR "INLINE"

Este especificador debe ser utilizado precediendo a la declaracin de una


funcin, y constituye una sugerencia al compilador para que ste sustituya
"en lnea" cualquier uso de la funcin por el cuerpo de sta, en lugar de
efectuar la llamada de funcin. Se logra, as, evitar la penalizacin en tiempo
de ejecucin determinada por la llamada a la funcin.

Dado que se trata de una recomendacin, el compilador podra ignorar la


especificacin inline en una funcin por muy distintas razones: por ser
recursiva, por ser demasiado larga, por contener una instruccin goto, etc.
En general estos detalles dependen de la implementacin del compilador. En
Visual C++ se afirma que el mecanismo de "inlining" no posee restricciones:
o sea, toda llamada a una funcin declarada como "inline" ser sustituida por
su cuerpo, sin excepcin alguna. En realidad esto no tiene por qu ser una
ventaja: en ARM se establece la accesoreidad del uso de tal especificador.

Hay que notar, ant todo, que una funcin inline no es igual a una macro del
preprocesador, pues en stas no se produce chequeo alguno de tipos. Se
obtiene, pues, "lo mejor de dos mundos". Conviene destacar, por otro lado,
que el especificador no surtira efecto sobre una declaracin de funcin, pues
no habra cdigo que sustituir.

El especificador inline est especialmente indicado para optimizar funciones


pequeas, de pocas lneas y frecuente uso. La eficacia de este cualificador
puede ser comprobada -es un decir- en el siguiente cdigo:

void funcionNormal() {};


inline void funcionInline() {};
void chequeaFuncionInline()
{
Cronometro cronometro;
cronometro.empieza();
for (long contador = 1; contador < 10000000; contador++)
funcionNormal();
cronometro.imprime();
cronometro.empieza();
for (long contador = 1; contador < 10000000; contador++)
funcionInline();
cronometro.imprime();
cronometro.cierra();
}

En la anterior funcin se hace uso de un objeto cronometro, que se declara localmente y


se inicializa antes de cada bucle imprimiendo el lapso de tiempo transcurrido seguidamente. Aunque el resultado

C++/OOP: UN ENFOQUE PRCTICO Pgina 57/297


depende de la implementacin, el bucle conteniendo la funcin inline ser ejecutado una media de 4 veces ms
rpido. El lector podra chequear su sistema sustituyendo lo relacionado con el objeto cronometro por un ms
simple contador horario.

Por qu no declarar entonces, visto lo anterior, todas las funciones como


inline? Pues porque, en palabras del Dr. Stroustrup, el mecanismo de "inli-
ning no es una panacea". El abuso de esta especificacin obligara, por
ejemplo, a recompilar todos los mdulos en que aparecieran funciones
inline cuando stas sufrieran alguna modificacin. El hecho, por otra parte,
de que el compilador deba mantener en memoria el cdigo de las funciones
inline puede ocasionar colapsos por falta de memoria en algunas
implementaciones C++. El tamao del cdigo puede aumentar extraordina-
riamente y se debe recordar, por ltimo, que la definicin de funciones
inline en los archivos de cabecera acarrear la publicitacin del cdigo
fuente de las mismas.

SOBRECARGA DE FUNCIONES

C++ permite el uso en el mismo mbito de igual nombre de identificador


referido a funciones con distintos argumentos. La llamada a la funcin
apropiada es resuelta por el compilador, de forma que se produce una
deseable homogeneizacin del cdigo, como ya vimos al describir el
polimorfismo en un captulo anterior.

La sobrecarga de funciones se origina, simplemente, al declarar una funcin,


anteriormente ya declarada, con distinto nmero y/o tipo de argumentos, no
afectando al tipo de retorno de las funciones. De esta manera tenemos que
dada, por ejemplo, la funcin

long multiplicar( int& multiplicando, int multiplicador );

las siguientes declaraciones seran calificadas como errores por redeclaracin


en tiempo de compilacin, pues difieren de la primera nicamente en el tipo
de retorno o en tipos asimilables por conversiones triviales:

void multiplicar( int, int ); // error


long multiplicar( int variable1, variable2 ); // error
long multiplicar( int, int& ); // error
// recordemos que un 'typedef' no es un tipo separado
6
// sino simplemente un 'sinnimo' de un tipo

6
Atencin a esta caracterstica! A pesar que la afirmacin es rigurosamente
exacta, el siguiente cdigo (y otros parecidos) podr ser compilado sin problemas
con Borland C++ 3.X y 4.0, mientras que AT&T C++ 3.0 -de acuerdo con lo
establecido en ARM- flagelara como errores las dos ltimas lneas:

int pruebaBorland( int );


typedef int Entero;

C++/OOP: UN ENFOQUE PRCTICO Pgina 58/297


typedef int Entero;
long multiplicar( Entero, Entero ); // error

mientras que las siguientes declaraciones seran reputadas como correctas


(ntese que algunas de tales funciones son ellas mismas tambin sobre-
cargadas correctamente), siempre que tengamos la precaucin, como vimos
cuando repsabamos los parmetros por defecto, de no compilarlas junto
con las anteriores, a fin de evitar la posibilidad de "asunciones parciales" por
parte del compilador:

float multiplicar( float, float );


long multiplicar( int, int, int=1 );
long multiplicar( long, int );
double multiplicar( double, double, double);
short multiplicar( short, short );
void multiplicar();
long multiplicar( volatile int&, int );
long multiplicar( int&, const int& );
long& multiplicar( int, int* );
long& multiplicar( int, const int* );
char* multiplicar( char, int );
char* multiplicar( int, char );
// Atencin: 'Entero' es un tipo distinto de 'int' en C++
enum Entero ( cero, uno, dos, tres ); C++
long multiplicar( Entero, int );

Notemos que, si bien son distintos, los prototipos siguientes:

long multiplicar( int, int ); // funcin original


long multiplicar( int, int, int=1 );// funcin sobrecargada

causarn que la primera de las siguientes llamadas concretas a la funcin

multiplicar( 2, 1 ); // error: ambigedad


multiplicar( 2, 1, 1 ); // ok

sea declarada como error, pues encaja con cualquiera de los dos prototipos.
Aqu surge la cuestin sobre la idoneidad de tal sobrecarga, resultando as
que o bien se aade el tercer argumento al prototipo de la funcin original,

int pruebaBorland( Entero );


Entero pruebaBorland ( int );

Se trata aqu de una cuestin de adaptacin del compilador a los estndares del
lenguaje. Debemos recordar que, aun siendo Borland C++ 4.0 la implementacin de
C++ para PC's ms ajustada a los "estndares", sta se basa slo parcialmente en
AT&T C++ 3.0 y en el borrador del estndar de C++ proveniente de X3J16. Deberemos
esperar a futuras versiones para ver si se solucionan estas "curiosidades". Mi
consejo: el lector deber evitar las construcciones del tipo expuesto, pues de otra
manera su cdigo en el futuro podra no ser portable.

C++/OOP: UN ENFOQUE PRCTICO Pgina 59/297


eliminando la sobrecarga, o bien se puede suprimir el parmetro formal por
defecto, eliminando la ambigedad en la concreccin.

En el caso de que en una llamada a una funcin sobrecargada no se


produzca una correspondencia exacta con el tipo de cada uno de sus
argumentos, se buscar la mejor de las correspondencias posibles con los
prototipos disponibles aplicando las siguientes reglas ordenada y
consecutivamente a cada uno de los argumentos de la funcin llamada:

1) Conversin trivial: tipo pasa a tipo&, tipo& pasa a const tipo&, tipo&
pasa a volatile tipo&, tipo* pasa a const tipo* y tipo* pasa a volatile tipo*.

2) Promocin: los tipos char, unsigned char, short int e int son
promocionados a int si int puede contenerlos, o a unsigned int de otra
manera; float pasa a double y double pasa a long double.

3) Conversin standard: cualquier tipo numrico pasa a otro tipo numrico;


las enumeraciones pasan a tipos numricos; el "cero" se convierte en
instanciacin de puntero a cualquier tipo o de tipo numrico; un puntero a
un tipo determinado se convierte en un puntero a void*; los punteros,
referencias y objetos de clases derivadas pblicamente se convierten en
punteros, referencias y objetos a clases base de la jerarqua; un puntero de
una clase base se convierte en puntero a una de sus clases derivadas
pblicamente.

4) Conversin definida-por-el-usuario: si se trata de un tipo definido por


una class y en sta se ha implementado una funcin miembro o friend de la
forma operator tipo(), denominada operador de conversin, se aplicar el mtodo por ella definido al tipo del
argumento y se buscar la correspondencia del nuevo tipo en los prototipos de la funcin sobrecargada.

5) Correspondencia con elipsis: un argumento de cualquier tipo establecer


correspondencia con una funcin prototipada con lista de argumentos ( ... ).

Hay que notar que si bien los intentos de ajustar un valor a un tipo de
argumento formal se dan mediante la aplicacin ordenada de los diferentes
formatos de conversin, no existe prevalencia de conversin dentro de cada
uno de stos, de forma que debemos desechar la idea intuitiva de que el
compilador efectuar la conversin que requiera menos esfuerzo o tiempo:
en realidad el compilador probar todas las posibilidades dentro de cada uno
de los cinco tipos de conversin, declarando un error de ambigedad cuando
sea posible realizar ms de una correspondencia entre valor y tipo de
argumento. No existe, tampoco, precedencia en la conversin en razn del
orden de declaracin de las funciones sobrecargadas. As, por ejemplo,
dadas las siguientes declaraciones:

extern int funcion( int );


extern int funcion( short );

sern declaradas como errores las llamadas:

C++/OOP: UN ENFOQUE PRCTICO Pgina 60/297


funcion( 1L ); // error: long pasa a int
// a short indistintamente
enum boolean { FALSO, VERDADERO };
funcion( FALSO ); // error: enum pasa a int
7
// a short sin precedencias

Esto es, aplicando las conversiones estndar detalladas anteriormente, los


valores de tipo long y enum se convertirn en cualquier otro tipo numrico
para ajustarse a los tipos de los parmetros formales de las funciones sobre-
cargadas -en este caso int o short-, de forma que, al darse ms de una
posibilidad, se seala como error por ambigedad la llamada concreta.

En las versiones anteriores a la AT&T C++ 3.0 se observaba una regla de


precedencia en la aplicacin de conversiones de ajuste: las conversiones que
requeran la aplicacin de variables temporales ern consideradas de un
orden inferior al de las conversiones que no las requeran, tomando stas
precedencia sobre aqullas. As, dadas

void hazNoSeQue( long );


void hazNoSeQue( char& );

en el siguiente cdigo:

int numeroDeOjos = 1; // Ojos de un cclope


char letraErotica = 'S'; // Desfasada calificacin moral
// la siguiente llamada ser calificada como AMBIGA
// bajo C++ 3.0: no hay precedencia de conversin.
hazNoSeQue( numeroDeOjos ); // OK bajo C++ 2.0:
// llama a hazNoSeQue( long )
hazNoSeQue( letraErotica ); // OK: llama a hazNoSeQue(char&)

observamos que dado que la conversin de int a char& requiere el uso de


una variable temporal, la conversin a long hubiera tomado precedencia
bajo C++ 2.0 y se hubiera producido una llamada sin ambigedad a la
funcin con tal argumento. En C++ 3.0, sin embargo, se producira error
por ambigedad en la resolucin de la sobrecarga.

Se aplicarn reglas de precedencia, igualmente, en la conversin de objetos,


referencias o punteros de clases derivadas con carcter pblico a objetos,
punteros o referencias a sus clases base, en razn de la proximidad
jerrquica de las clases involucradas en la conversin.

7
Surge aqu de nuevo lo ya apuntado en la nota anterior: esta lnea compilar
sin error con Borland C++ 3.X, aunque s fallar en el cfront 3.0 de AT&T.
Afortunadamente Borland C++ 4.0 ha subsanado este desajuste, y correctamente
origina un error en compilacin por ambigedad en tal lnea.

C++/OOP: UN ENFOQUE PRCTICO Pgina 61/297


Cabra preguntarse: Por qu esta parafernalia de conversiones? Por qu
no aplicar reglas ms simples, como la del mnimo esfuerzo? Realmente el
grueso de las reglas conviene a las conversiones standard en C y tambin,
por compatibilidad, en C++, pues los ajustes definidos por los operadores
de conversin en las clases, propios de C++, se limitan a uno por tipo. La
mejor manera de evitar ambigedades es, en lo posible, evitarlas: si
deseamos evitar problemas con un determinado tipo, debemos implementar
una funcin sobrecargada que acepte ese tipo exacto de argumento.

El lector podr encontrar una descripcin exhaustiva de las cinco reglas de


conversin en sobrecarga de funciones en el apartado 13.2 de ARM.

LOS OPERADORES "NEW" Y "DELETE"

Las operaciones de manejo de la memora libre se realizan en C++ mediante


los operadores new y delete.

El operador new se utiliza para alojar en memoria un objeto de tipo predefi-


nido o definido-por-el-usuario (instanciacin de una clase), reservando
primero la suficente cantidad de memoria de almacenamiento libre, iniciali-
zndolo depus y devolviendo, al fin, un puntero al mismo. De esta forma la
creacin dinmica de un array de char se realizara de la forma:

const MAX_ARRAY = 100;


char* punteroAArrayDeChar, pc;
pc = punteroAArrayDeChar = new char[ MAX_ARRAY ];
*pc = 'W';

Veamos tambin, de igual manera, otras posibilidades sintcticas igualmente


vlidas:

int* pEntero1, pEntero2;


pEntero1 = new int; // construye un objeto de tipo int
pEntero2 = new( int ); // notacin funcional
// equivalente a la anterior
int OK = ( pEntero1 != pEntero2 ); // cierto SIEMPRE;

El operador global ::new aparece originalmente declarado de la siguiente


forma:

void* operator new( size_t tamanoEnBytesDelObjetoAAlojar );

y el hecho de que en los ejemplos no hayamos tenido que explcitamente


declarar el tamao en bytes de la memoria libre requerida se debe a que es
el sistema el que automticamente se responsabiliza de tal clculo y asigna-
cin (de la misma forma que en el lgebra de punteros). Debido a tal espe-
cial circunstancia cualquier sobrecarga de este operador debe devolver
void* y poseer un primer argumento de tipo size_t (typedef establecido en

C++/OOP: UN ENFOQUE PRCTICO Pgina 62/297


"stddef.h") representando el tamao en bytes a alocar por el sistema. El
lenguaje establece, como caracterstica estndar 8, la siguiente sobrecarga
predefinida de ::new:

void* operator new( long tamanoEnBytesDelObjetoAAlojar,


void* direccionDeMemoria );

la cual nos permite aplicar un rea de memoria predeterminada al


alojamiento del nuevo objeto:

// asignacin estndar de ::new


char* peliculaDeHoy = new char[ 30 ];
// seguidamente deseamos aprovechar el almacenamiento libre
// asignado al ttulo de la pelcula de hoy
// para el almacenamiento del ttulo de la pelcula de maana.
char* peliculaDeManana = new( peliculaDeHoy ) char[ 30 ];
// seguidamente se "nulifica" el puntero "peliculaDeHoy"
// para evitar futuras e indudablemente peligrosas
// desreferenciaciones
peliculaDeHoy = 0;

Ntese, de nuevo, que en el ejemplo slo hemos proporcionado como


argumento el puntero a la direccin de la memoria libre a reutilizar,
ocupndose de la determinacin del tamao del espacio apuntado por el
identificador peliculaDeHoy el propio sistema. El desarrollador sera en este caso responsable de la
correspondencia entre el tamao del objeto a almacenar y la memoria disponible para su "reutilizacin".

Cuando el operador new se utiliza para el alojamiento de instancias de


clases (objetos), como por ejemplo

ClaseEjemplo *punteroAObjetoDeClaseEjemplo = new ClaseEjemplo;

se pone en marcha el siguiente esquema secuencial:

1) Se busca una definicin sobrecargada del operador new en la clase del


objeto a crear (en este caso ClaseEjemplo), de la forma

void* ClaseEjemplo::operator new( size_t ) {


// aqu vendra el cdigo apropiado
}

y se ejecuta el cdigo de la misma.

8
El adjetivo estndar relativo al lenguaje C++, como se ha advertido varias
veces, se refiere a las caracters ticas derivadas de las implementaciones de AT&T.
En el presente caso la sobrecarga del operador ::new aparece predefinida en el
archivo "new.h", mientras que en otras imple mentaciones pudiera no existir tal
declaracin, debido, entre otras cosas, a la facilidad con que tal sobrecarga puede
ser implementada por el propio desarrollador.

C++/OOP: UN ENFOQUE PRCTICO Pgina 63/297


2) Si la definicin anterior no se encuentra en la clase del objeto, se procede
a su bsqueda en las clases de las que sta pblicamente deriva, para
seguidamente ejecutarla. Como ms adelante veremos en la derivacin de
clases, esta caracterstica originar la aplicacin de un esfuerzo adicional en
el diseo de la sobrecarga del operador en esquemas de derivacin pblica.

3) Si no se encuentra ninguna redefinicin del operador new, entonces se


aplicara el operador global ::new(), que invocara el constructor apropiado
para el nuevo objeto (en este caso, el constructor por defecto:
ClaseEjemplo::ClaseEjemplo() ), asignando seguidamente un puntero al mismo al puntero
punteroAObjetoDeClaseEjemplo.

Cuando lo que se desea crear es, por el contrario, un array de objetos,


como en el cdigo siguiente:

ClaseEjemplo *punteroAArrayDeObjetos = new ClaseEjemplo[numero];

entonces se producira nicamente una llamada al operador global ::new(),


que invocara el constructor apropiado para cada uno de los nuevos objetos,
desde ClaseEjemplo[0] hasta ClaseEjemplo[numero-1], para despus asignar un puntero a tal array al puntero
punteroAArrayDeObjetos.

Si tenemos en cuenta la comparacin, por otra parte siempre presente, entre


tipos incorporados y tipos definidos-por-el-usuario (cuales son las clases),
encontramos que en el caso de arrays de tipos predefinidos (int, char, etc.)
es el entorno del lenguaje el que "recuerda" el tamao del array declarado.
De igual lgica forma, a partir de AT&T 2.1, es el entorno C++ el encargado
de guardar el tamao de un array de objetos. En versiones anteriores,
empero, el desarrollador deba cargar con tal responsabilidad.

Hasta ahora hemos visto la creacin de nuevos objetos por medio de los
constructores por defecto de las clases de las que seran instanciaciones. La
creacin de nuevos objetos usando otros constructores puede realizarse
mediante la sintaxis:

ClaseEjemplo* punteroAObjetoClaseEjemplo =
new ClaseEjemplo( argumento1, ... );

En este caso se invocara el constructor adecuado a la lista de argumentos


provista.

Debe recordarse que el operador new intenta alojar el nuevo objeto en el


rea de memoria de almacenamiento libre: si no existiera suficiente memoria
para el alojamiento del objeto, el operador global ::new devolver cero
(0). El desarrollador es responsable, pues, de generar cdigo de chequeo del
resultado de la aplicacin del operador. El sistema, en realidad, utiliza el
siguiente mecanismo : si ::new falla en el alojamiento de un objeto (por
haberse agotado el almacenamiento libre), se produce el chequeo de un

C++/OOP: UN ENFOQUE PRCTICO Pgina 64/297


manipulador predefinido del tipo "puntero a funcin sin argumentos y con
valor de retorno void" denominado _new_handler, declarado en el archivo
de cabecera standard "new.h" de la siguiente forma:

extern void( *_new_handler ) ();


_new_handler = 0; // asignacin a CERO por defecto

de forma que si tal manipulador apunta a cero, ::new devuelve cero,


mientras que, en caso contrario, se produce una llamada a la funcin
apuntada por _new_handler. Podemos, pues, suprimir el chequeo directo
del resultado del operador ::new mediante la reasignacin al manipulador
de la direccin de una funcin destinada al manejo de los errores de
alocacin de objetos en memoria. Tal asignacin la podemos realizar bien
directamente

extern void errorPorMemoriaLibreAgotada();


_new_handler = errorPorMemoriaLibreAgotada;

bien a travs de una funcin especfica para ello declarada, tambin, en


"new.h":

void (* set_new_handler( void ( * )() ) )();

de la siguiente forma (siguiendo el ejemplo anterior):

set_new_handler( errorPorMemoriaLibreAgotada );

Pero, qu ocurre tras la ejecucin de la funcin apuntada por


_new_handler? Pues bien, el sistema asume que se ha solucionado el
error de alocacin generador de la llamada y si explcitamente no se codifica
en la funcin apuntada por el manipulador una llamada a, por ejemplo, la
funcin estndar exit() (incluida en el archivo C "stdlib.h"), al devolver el
control al operador global ::new ste intentar realizar de nuevo el
alojamiento en memoria, fallar en el intento, llamar de nuevo a la funcin
apuntada por el manipulador y vuelta a empezar, originando posiblemente
un bucle infinito equivalente a una condicin de error irrecuperable. Por
supuesto una sobrecarga adicional del operador new podra manejar de
forma ms adecuada esta situacin. Como quiera, por otra parte, que estos
errores se producen nicamente en tiempo de ejecucin, quiz uno de los
aspectos ms interesantes de esta tcnica lo constituya la posibilidad de
implantar subrepticiamente un sistema parcial de "captacin de excepciones"
que podra ser utilizado, como veremos, en el chequeo de constructores de
clases.

Pareja dicotmica del operador new, el operador delete se usa, en contra-


partida, para la restitucin al rea de almacenamiento libre de la memoria
anteriormente utilizada por mediacin de new. Su sintaxis es transparente,
aplicada sobre punteros a objetos almacenados mediante new:

C++/OOP: UN ENFOQUE PRCTICO Pgina 65/297


// Se declaran distintos punteros
int* punteroAInt;
float* punteroAArrayDeFloat;
ClaseEjemplo* punteroAObjectoDeClaseEjemplo;
ClaseEjemplo* punteroAArrayDeObjetosClaseEjemplo;
// ...
// Seguidamente se aplica el operador new
punteroAInt = new int;
punteroAArrayDeFloat = new float[ 3 ];
punteroAObjetoDeClaseEjemplo = new ClaseEjemplo;
punteroAArrayDeObjetosClaseEjemplo = new ClaseEjemplo[ 6 ];
// ..
// Se procede al desalojo de la memoria utilizada
// por las variables y arrays
delete( punteroAInt );
delete[] punteroAArrayDeFloat;// delete[3] punteroAArrayDeFloat
//en AT&T 2.0
delete punteroAObjetoDeClaseEjemplo;
delete[] punteroAArrayDeObjetosClaseEjemplo;// delete[ 6 ] ...
// en AT&T 2.0 y anteriores

El operador global ::delete aparece originalmente declarado como

void operator delete( void* punteroAMemoriaADesalojar );

incorporndose al estndar del lenguaje tambin la siguiente sobrecarga:

void operator delete( void* punteroAMemoriaADesalojar,


size_t tamanoEnBytesAreaMemoria );

responsabilizndose el sistema (como en el caso del operador new) de la


inicializacin del segundo argumento, que representa el tamao de la
memoria a desalojar. De igual manera, las sobrecargas de este operador
debern devolver void y significar el menos un primer argumento de tipo void*, observndose tambin
que si se aplican ms argumentos el segundo de ellos habr de ser forzosamente de tipo size_t y para uso del
sistema.

De forma pareja a como ocurre con el operador new, cuando se aplica el


operador delete a un objeto de tipo definido-por-el-usuario, como en el
ejemplo

delete punteroAObjetoDeClaseEjemplo;

en primer lugar se busca una posible implementacin de, por ejemplo:

void ClaseEjemplo::operator delete( void* ) {


//aqu vendra el cdigo
}

en la clase del objeto; seguidamente, si no se ha encontrado, en las clases


base pblicas de la clase actual del objeto; por ltimo, si no se ha sobrecar-

C++/OOP: UN ENFOQUE PRCTICO Pgina 66/297


gado el operador delete, se aplica el operador global ::delete(), el cual
invocar el destructor apropiado para el objeto (en este caso, el destructor
por defecto ClaseEjemplo::~ClaseEjemplo()).

Vemos que la sintaxis vara en el caso de tener que liberar la memoria


ocupada por arrays. En tal supuesto el cdigo

delete[] punteroAArrayDeObjetosClaseEjemplo;

simplemente aplica el operador global ::delete() al array, invocando el


destructor apropiado para cada uno de los objetos, desde punteroAArrayDeObjetos-
ClaseEjemplo[0] hasta punteroAArrayDeObjetosClaseEjemplo[5]. Como vimos anteriormente, es responsabilidad
del sistema el mantenimiento del tamao del array, de forma que no tenemos que explicitarlo en la sintaxis del
operador.

En versiones anteriores a C++ AT&T 2.1 el compilador exiga el tamao del


array al que se habra de aplicar el operador delete, debiendo codificar lo
siguiente:

delete [ 6 ] punteroAArrayDeObjetosClaseEjemplo;

Esta codificacin es soportada por C++ como un anacronismo y actualmente


origina, a lo sumo, un aviso o warning del compilador, aunque podra perju-
dicar la portabilidad del cdigo a futuras versiones de C++.

Es importante notar, en aras de la claridad conceptual, que sobre un array


de objetos de tipo incorporado o no, siempre actuarn los operadores
globales ::new y ::delete, pero que sobre un objeto de tipo
ArrayDeClasesEjemplo definido, por ejemplo, as:

Class ArrayDeClasesEjemplo {
private:
ClaseEjemplo** punteroAArrayDeClasesEjemplo;
//...
}

la aplicacin de los operadores ser la siguiente:

ClassArrayDeClasesEjemplo* punteroAObjetoArray;
punteroAObjetoArray = new ClassArrayDeClasesEjemplo;
delete punteroAObjetoArray;

pues no se trata aqu de un array de objetos, sino de un objeto con


caractersticas de array. Se buscarn primero, por tanto, posibles
sobrecargas de los operadores antes de aplicar los operadores globales. Se
puede comprender ahora que el compilador trata a un array como un objeto
incorporado de agregacin de objetos y que, como tal, no redefine los
operadores new o delete, debiendo aplicar en las operaciones con ste los
operadores globales. Persiste, pues, la homogeneizacin entre tipos predefi-
nidos y clases.

C++/OOP: UN ENFOQUE PRCTICO Pgina 67/297


Es conveniente aadir que el sistema, a pesar de mantener o "recordar" el
tamao de los arrays de objetos, no es "inteligente" con respecto a los
identificadores de los arrays. Esto es, la aplicacin del operador delete
requerir siempre9 (en el caso de arrays) de la sintaxis [], que indicar al
entorno que se pretende "destruir" un array ms que un objeto. Supuestas
las declaraciones anteriores, si ejecutamos la siguiente lnea

delete punteroAArrayDeObjetosClaseEjemplo;

lo que se generar es una llamada al constructor ClaseEjemplo::~ClaseEjemplo() para el


objeto *punteroAArrayDeObjetosClaseEjemplo (o, lo que es lo mismo, punteroAArrayDeObjetosClaseE-
jemplo[0]), pero quedarn sin destruir los objetos restantes del array (desde punteroAArrayDeObjetos-
ClaseEjemplo[1] hasta punteroAArrayDeObjetosClaseEjemplo[5]).

El operador global ::delete ha de aplicarse a punteros a objetos cuyo


alojamiento haya sido procurado por el operador global ::new, pues de otra
forma el resultado ser indefinido. El lenguaje asegura, por otra parte, la
aplicacin del operador ::delete a punteros apuntando a cero (NULL) como
una operacin siempre vlida.

Por supuesto los operadores globales son siempre accesibles por medio del
operador ::, de forma que pueden de esta manera ser invocados en
sustitucin del posible operador sobrecargado que en razn de las reglas de
mbito conviniera aplicar.

MIGRACIN DE ANSI C A C++: REGLAS BSICAS

Ya hemos repasado las diferencias bsicas entre ANSI C y C++, de forma


que tenemos una suerte de breviario para procurar la transicin de uno al
otro lenguaje. Dadas las caractersticas de eficacia, funcionalidad y soporte
de OOP que provee C++ es lgico pensar que en los prximos aos se
producir una migracin masiva desde sistemas C hacia C++. No sobran,
sin embargo, algunas reglas elementales que, sin duda, tornarn ms
amable y suave tal adaptacin:

- El propio Bjarne Stroustrup ha afirmado que C++ no es la medida de


todas las cosas: existen problemas que no tienen una buena solucin en
C++. Por esto es necesario, ante todo, determinar qu partes de un sistema
software son susceptibles de mejora mediante el uso de las caractersticas de
este nuevo lenguaje, para inmediatamente evaluar los beneficios del nuevo
desarrollo contra los costos del mismo. Si un sistema funciona bien no hay
ninguna razn vlida para cambiarlo.

9
Con la nica excepcin de arrays unidimensionales (vectores) que carezcan
del operador delete y de destructor, como, por ejemplo, los vectores de tipos
incorporados (int, float, etc.)

C++/OOP: UN ENFOQUE PRCTICO Pgina 68/297


- No debe mezclarse indiscriminadamente el cdigo C++ con el existente en
C, pues esto normalmente causar ms problemas que beneficios: el
mantenimiento se tornar an ms costoso y el desarrollo en C++ se ver
sin duda constreido por su forzado interfaz con C. Debe mantenerse, pues,
una limpia separacin entre las implementaciones de ambos lenguajes, lo
que no quiere decir que no se utilicen en C libreras y caractersticas de
C++; ms bien lo que se sugiere es que la incorporacin de C++ sea
modular e incremental.

- El uso por C de libreras de C++ debiera realizarse siempre, en lo posible,


a travs de funciones C compiladas en los mdulos C++, las que, a su vez,
accederan a las funciones y mtodos de la librera C++. De esta forma se
evitarn problemas de portabilidad derivados de las implementaciones
especficas de la codificacin interna (name mangling) por el enlace de
tipo-seguro. As, por ejemplo, podra definirse un archivo C++ que sirviera
de interface a C para el uso de funciones de algebra de matrices de la
siguiente forma:

extern "C" int determinante( Matriz* miMatriz) {


return miMatriz.determinante();
}

de esta forma podra llamarse sin problemas desde cualquier archivo C a la


funcin determinante declarndola de la siguiente forma:

extern determinante( void* );

- Existen funciones de la librera estndar de C ms eficientes, para determi-


nados propsitos, que las equivalentes en C++. As, por ejemplo, pudiera
elegirse printf() en lugar de cout.operator<<(...) en una aplicacin
especfica. No es C, pues, el "hermano pobre" de C++ ni tampoco ste es el
verdugo de aqul. Debe elegirse siempre por tanto, en consonancia con el
espritu del nuevo lenguaje, la implementacin ms efectiva.

C++/OOP: UN ENFOQUE PRCTICO Pgina 69/297


DONDE "LA CLASE"
5
SE EVIDENCIA

En el presente captulo abordaremos formalmente la clave del acercamiento


de C++ a la OOP: las clases. Alrededor de ellas gira la prctica totalidad de
las tcnicas del nuevo paradigma de programacin, de tal forma que
focalizan el tratamiento de las nuevas caractersticas del lenguaje.
Empezaremos, por decirlo as, con algo de teora, algo de comentario y un
tanto ms de ejemplos, de tal forma que no nos complicaremos demasiado
con el "qu" y nos centraremos ms en el "cmo". O sea, que se podra
escribir y escribir sobre las caractersticas del lenguaje relacionadas con las
clases, pero lo cierto es que eso ya est hecho ("El Manual de Referencia de
C++ Anotado", cariosamente conocido como "ARM" es un libro
-indispensable, por otro lado- dedicado, prcticamente, slo a ello). En
definitiva, intentaremos asir el concepto que se esconde tras "la clase" para
as poder usarla con juicio. Con juicio? Bueno, en la comunidad C++
circula una historia, a estas alturas ya muy deformada, que cuenta cmo un
equipo de desarrollo que hasta entonces trabajaba en C, con su C++
flamante y sin estrenar, tuvo que acometer su primer proyecto en el nuevo
lenguaje: los programadores se dijeron "para qu analizar o disear? No
estamos en C++? Hagamos lo que sea, y de seguro que ser OOP!". Al
cabo de seis meses se encontraron en que, virtualmente trabajando cada
uno por su lado, haban desarrollado la friolera de ms de 3.000 clases, cada
una de ellas repleta de cdigo usando casi todas las caractersticas posibles
del lenguaje, a cual ms "inservible". Lo cierto es que tardaron pocas horas
en decidir el arrinconamiento de este ingente trabajo y volvieron a comenzar
el proyecto en puro C, jurndose ciertas barbaridades y un reparto de odios
eternos a ciertos "gurs" de la OOP. Esto sucedi en USA, por supuesto, y lo
cierto es que el nombre de la compaa ha quedado grabado en fuego en la
breve historia de este lenguaje, al igual que sucedi con el causante del
incendio en la Biblioteca de Alejandra. Lo mejor es evitar el nombre en
ambos casos. La moraleja? Ah va: "la clase" est reida con lo superfluo y
la tontera (y lo cierto es que esto lo podra haber muy bien proclamado el
mismsimo Lord Brummel). Cuando se aprende un nuevo lenguaje de
programacin, inmediatamente se intentan aplicar todas las tcnicas
estudiadas, lo que constituye una barbaridad pareja a la de, tras un curso de

C++/OOP: UN ENFOQUE PRCTICO Pgina 70/297


Cocina, intentar cocinar siempre con la totalidad de las viandas: imaginen el
pastiche. As que ... tranquilidad! y seamos prcticos. Pero, antes de entrar
en materia, y aun a riesgo de resultar dolorosamente trivial, vamos a echarle
un vistazo a una materia "opinable" por naturaleza: el estilo. Y por qu?
Bien, resulta contraproducente que C++ pretenda clarificar mediante
distintos mecanismos el aspecto del lenguaje y que ste, sin embargo,
aparezca como un plato de tagliatelli demasiado hervidos. Fiense de mi
brevedad.

ALGUNAS NOTAS SOBRE ESTILOS DE CODIFICACIN

Existen en la actualidad, bsicamente, tres diferentes estilos puros de


indentacin del cdigo C (y, por ende, C++), distinguindose significati-
vamente en la colocacin de las llaves ({}) que encierran bloques de cdigo
tales como los de funciones, estructuras de control, bucles, etc.

Por un lado, y en primer lugar, est el viejo y compacto estilo Kernighan-


Ritchie, que abre llave al final de la lnea de la cabecera de la estructura de
control y la cierra bajo sta y a su nivel, indentando el cdigo un nivel.

switch ( respuesta ) {
case NO:
cout << "La respuesta es NO";
default:
cout << "No sabe. No contesta";
}

Otro estilo abre llave justo debajo de la cabecera de la estructura de control


indentndola un nivel, formando vertical con el cdigo del bloque y con el
cierre de la llave.

for ( int iterador= 0; iterador < 10; iterador++ )


{
cout << iterador;
}

El ltimo estilo, al fin, abre llave justo debajo de la cabecera de la estructura


de control y a su nivel, indenta el bloque un nivel y cierra llave al nivel de la
apertura.

void Empleado::estableceNombre( char* cadenaNombre )


{
nombre = cadenaNombre;
}

Dado que la eleccin de estilo de indentacin es una materia de gusto


personal y de claridad en la lectura del cdigo, mi modesto consejo es que,
una vez escogidas las directrices que harn ms legible nuestro cdigo -sean
las que sean- debern ser empleadas sin lagunas. O sea, que no debe

C++/OOP: UN ENFOQUE PRCTICO Pgina 71/297


cambiarse de estilo en cada mdulo; ni siquiera en cada programa. En lo
que a m respecta y como el lector ya habr podido apreciar, uso una
mixtura propia y coherente de los estilos anteriores, conservando el estilo
Kernighan-Ritchie para las sentencias de control (for, switch, do-while, etc.),
mientras que utilizo el tercer estilo (llaves al nivel de la estructura y cdigo
indentado un nivel) para las funciones, mtodos, constructores y
destructores. Vemoslo:

void imprimePares( long limiteSuperior )


{
for ( long i = 1; i <= limiteSuperior; i++ ) {
if ( i & 2 == 0 )
cout << i << "\n";
}
}

Prefiero, por otro lado, anteponer el tipo de retorno al nombre de la funcin


en la misma lnea

int main( int, char** ) {}

en lugar del tambin aceptable estilo

int
main( int, char** ) {}

Ahora un tema que desata polmicas: cuando se declaren distintas variables


en una lnea (algo, por otra parte, usualmente del todo innecesario y que
siempre es mejor evitar 10), normalmente adscribir el operador de puntero
(*) al identificador de la variable en lugar de al del tipo, para evitar errores
de lectura. As puede verse claramente que

char *cadena, letra;

declara un puntero a char (cadena) y un simple carcter (letra), mientras que


esta otra sintaxis:

char* cadena, letra;

podra inducir a pensar que se estn declarando dos punteros a char (los
espacios no son considerados por el analizador lxico del compilador).

10
Pinsese que el compilador ignora, a efectos de optimizacin del cdigo, el
hecho de que unas determinadas variables hayan sido declaradas o no en la
misma lnea. La mana economizadora no proporciona, pues, a excepcin de en
algunos ejemplos triviales, ninguna ventaja apreciable, procurando, en la mayora
de los casos, una dificultad adicional para el lector. Mi consejo: eviten las
multi-declaraciones en una lnea! Reserven el ingenio para la concisin de los
algoritmos!

C++/OOP: UN ENFOQUE PRCTICO Pgina 72/297


Cuando no haya lugar a confusin se emplear indistintamente cualquiera de
las dos notaciones, en razn de conseguir la mxima inteligibilidad posible
en cada contexto. Lo cierto es, sin embargo, que yo siempre suelo utilizar el
operador de puntero adscrito al identificador del tipo (debido, sin duda, a la
gran cantidad de veces en que no he tenido ms remedio que hacerlo as en
los prototipos de funciones sin variables de "maquillaje"). Tambin es cierto
que para m no hay confusin posible en la lectura: magister dixit.

En cuanto a la codificacin de identificadores suelo usar el estilo de Smalltalk


(palabras seguidas sin separacin e iniciadas por letras maysculas, comen-
zando el identificador por minscula -a excepcin de los identificadores de
clases, que comenzarn por mayscula tambin-):

double calculaTasaInternaDeRetorno()
{
// aqu viene el cdigo
}

en lugar del viejo estilo C de la forma calcula_tasa_interna_de_retorno. La verdad es que el


ms compacto estilo Smalltalk se est imponiendo en C++. Pero, bueno, cada uno a lo suyo: si hasta existen
literatos que trabajan con mquinas de escribir!.

En teora la longitud de un identificador en C++ no tiene lmites, aunque


buena parte de los compiladores e intrpretes restringe dicha longitud a 32
caracteres como mximo. En orden a evitar problemas de portabilidad es
aconsejable el mantenimiento de dicho lmite, aunque a los efectos
pedaggicos de este libro tal restriccin no ser contemplada con demasiada
fruiccin.

Es aconsejable que la legibilidad del cdigo sea reforzada con la introduccin


(con las nica restriccin de la composicin tipogrfica) de espacios tras las
comas, entre los identificadores y los parntesis, y entre los operadores y los
identificadores.

Una ltima cuestin: es frecuente que en porciones cortas de cdigo el


desarrollador opte, en una curiosa furia economizadora, por significar en la
misma lnea la cabecera y el bloque de una determinada estructura, como
por ejemplo:

if ( condicion ) hazAlgo();

en lugar de disponerlo de la ms correcta forma:

if ( condicion )
hazAlgo();

la diferencia? Por un lado, con esta ltima sintaxis se es coherente con


cualquiera de los estilos de indentacin empleados (el cuerpo siempre se
indenta un nivel); por otro lado, a la hora de chequear el cdigo con un

C++/OOP: UN ENFOQUE PRCTICO Pgina 73/297


debugger, de esta forma podremos establecer un punto de ruptura en una
lnea u otra, fraccionando ms el anlisis y, por tanto, facilitando la deteccin
de posibles errores.

Bien: esto es todo. Dumas sola decir: "Prefiero los malvados a los imbciles;
aqullos, por lo menos, descansan". Y es que pocas cosas hay tan
insoportables como la gratuita e inmisericorde pesadez. Sepa de cualquier
manera el lector que existen textos enteros consagrados a esta materia
(como el de Indian Hill para C), as como manuales de corporaciones con
sus correspondientes normativas estilsticas "de empresa". Vayamos, por fin,
a C++ y a las clases.

PRIMER ACERCAMIENTO A LAS CLASES

Recordemos, retomando los escuetos conceptos apuntados en el captulo 2,


que una clase en C++ equivale a un tipo definido-por-el-usuario. No se trata
aqu de una especial "estructura de datos" representando una combinacin
determinada de tipos predefinidos, ni tampoco de una particular ligazn de
determinadas funciones con ciertos structs (como en C), sino de una
autntica abstraccin encapsuladora tanto de los datos como de los mtodos
intrnsecamente ligados a stos. Establezcamos, en un primer intento de
aproximacin formal al concepto, con un ojo crtico bsicamente dolido de
pedagoga, los siguientes ejemplos, que pasaremos a comentar lnea a lnea:

class ClaseVacia { // 1
}; //atencin al punto y coma
// tras la declaracin de la clase

class ClaseSinMetodos { // slo datos // 2


// equivale a un 'struct' de C
public: // 3
long numeroDNI;
char letraDNI;
private: // 4
char* nombreCliente;
};

class ClaseSinDatos { // 5
char* miClaveDeAcceso();//acceso PRIVADO por defecto // 6
public: //cambio a acceso PBLICO
int suma( int a, int b) { return a + b; } // 7
private: //acceso PRIVADO de nuevo
long edadDeLolaFlores;
};

class ClaseConDatosYMetodos { // 8
private:
long numeroDNI;
char letraDNI;
public:
char calculaLetraDNI( long );

C++/OOP: UN ENFOQUE PRCTICO Pgina 74/297


};

char ClaseConDatosYMetodos::calculaLetraDNI( long numero ) // 9


{
// funcin miembro definida fuera del mbito de
// descripcin de su clase y que accede a ste
// mediante el operador :: cualificador de mbito.
static char letra[] = "TRWAGMYFPDXBNJZSQVHLCKE";
return letra[ numero & 23 ];
}

1. En primer lugar observamos una "clase vaca". De la misma forma que en


la teora matemtica de conjuntos un conjunto vaco es distinto de un
conjunto conteniendo el elemento cero o an de un conjunto conteniendo al
conjunto vaco, en C++ las instanciaciones (objetos) de una "clase vaca"
cumplen que

ClaseVacia objetoDeClaseVacia, otroObjetoDeClaseVacia;


if ( sizeof( objetoDeClaseVacia ) != 0 )
cout << "SIEMPRE se cumple\n";
if ( &objetoDeClaseVacia != &otroObjetoDeClaseVacia )
cout << "SIEMPRE se cumple\n";
class ClaseNoVacia {
ClaseVacia* miPunteroAObjetoDeClaseVacia;
} objetoDeClaseNoVacia;
if ( sizeof( objetoDeClaseNoVacia )
!= sizeof( objetoDeClaseVacia ) )
cout << "SIEMPRE se cumple\n";

2. Una clase que no contenga mtodos posee una equivalencia funcional


similar a la de un struct en C, a excepcin de la cualificacin del acceso a los
miembros (en este caso datos) de la clase. Esto significa, sin ms, que no
podemos acceder directamente a lo que no est expresamente declarado
como pblico. Tenemos, as, informalmente, que:

ClaseSinMetodos miObjetoDeDatos;
miObjetoDeDatos.numeroDNI = 21428748; // OK
miObjetoDeDatos.letraDNI = 'Q'; // OK
miObjetoDeDatos.nombreCliente = "Landr" // ERROR: el acceso a
// nombreCliente es PRIVATE

3. La etiqueta public otorga la calificacin de acceso "pblico" a los


miembros de una clase comprendidos entre aqulla y la siguiente etiqueta de
calificacin de acceso o bien el fin de la declaracin de la propia clase.
Adelantaremos, extendiendo el ejemplo del punto anterior, que tanto esta
calificacin como la comentada en el punto 4 (private) estn ligadas a la
capacidad de acceso a los miembros de una clase tanto desde el protocolo
de descripcin de otra clase como desde un objeto de la misma u otras
clases.

C++/OOP: UN ENFOQUE PRCTICO Pgina 75/297


4. La etiqueta private supone una calificacin restrictiva de acceso con
respecto a la etiqueta public, reforzando el concepto de ocultacin de la
informacin tan enfatizado por la OOP. Como veremos ms adelante y
repitiendo lo anotado en el punto 2, un objeto no puede acceder
directamente a los miembros private de su clase:

// ...
claseSinMetodos objetoSinMetodos;
cout << objetoSinMetodos.numeroDNI; // Ok: legal;
cout << objetoSinMetodos.nombreCliente; // ERROR: acceso a
// miembro PRIVATE

5. La clase sin datos constituye la primera gran diferencia formal con


respecto a C: un objeto de una de tales clases posee nicamente servicios.
No debemos asimilar tal clase como una mera estructura contenedora o
aglutinadora de funciones que habran de ser compartidas por los objetos de
tal clase (que s se ajustara a una clase conteniendo nicamente funciones
static). Realmente cada objeto utiliza cada mtodo particularizado a travs de
un nivel adicional de indireccin, cual es el puntero implcito this, al que
dedicaremos un detallado comentario.

6. En una clase el acceso es private por defecto. Tal circunstancia puede ser
modificada por cualquier otra etiqueta expresa de cualificacin de acceso:
public o protected (un tipo especial de restriccin de acceso que
estudiaremos cuando veamos la derivacin de clases, y que por ahora
asimilaremos como que surte los mismos efectos que private), que pueden
aparecer sin restriccin de cantidad en cualquier seccin del cdigo de
descripcin de la clase.

7. La funcin suma(int,int) se ha definido dentro del cuerpo de descripcin de la clase. Esto supone que,
automticamente, tal funcin ser considerada inline y, por tanto, convenientemente macro-expandida
(observando, naturalmente, las restricciones que a tal respecto pueda imponer el compilador, segn establecimos
en el captulo anterior).

8. Una clase con mtodos y datos supone el modelo ms general, del que
los casos anteriores se constituyen en particularizaciones slo prcticas a
efectos didcticos, pero sin ningn valor formal (no existen por tanto, ahora
lo podemos decir, esos "tipos especiales" de clases, aunque s es posible que
se den clases sin datos, etc. O sea, existen mujeres fatales, pero esto no
quiere decir que las mujeres se dividan morfolgicamente en normales y
fatales, aunque otra cosa pensara Jardiel). Pensemos, as pues, en las clases
como colecciones de atributos y servicios comunes a bien-definidos
conjuntos arbitrarios de objetos, de forma que, en un nivel extensivo,
podran tambin verse como unas particulares colecciones conceptuales de
objetos. Recordemos que estamos hablando de tipos de datos sin lmite
cualitativo virtual, por lo que podran aplicarse a cualquier conjunto que
admitiera una definicin comprehensiva. Tomemos, por ejemplo, un
conjunto arbitrario de personas asistiendo a un concierto. El subconjunto de
tales espectadores que portara una camisa blanca podra ser identificado y

C++/OOP: UN ENFOQUE PRCTICO Pgina 76/297


encapsulado en la clase EspectadorMedioConCamisaBlanca, de tal forma que cada persona con estas
caractersticas sera un objeto de tal clase, independientemente del nmero de personas que en un determinado
momento se ajusten a tal descripcin.

9. La funcin calculaLetraDNI(long), declarada en el mbito de descripcin de la clase, se ha definido fuera


del cuerpo de ella. Normalmente esto se realiza as para mantener, en lo posible, una lmpida separacin entre el
interfaz o descripcin de la clase y la implementacin o definicin de sta, a la vez que para ocultar al observador
exterior los detalles de esta ltima, pues seran distribuidos como mdulos objeto. Definir la funcin de esta forma
conlleva dos inmediatos efectos: por un lado ya no se aplicar a sta la calificacin automtica de inline (aunque
sigue siendo posible declararla como inline aadindole el prefijo correspondiente "a mano"), as como, por otro,
se har necesario un mecanismo de identificacin y resolucin de mbito que permita a tal funcin gozar de
exactamente la misma operatividad que hubiera tenido si definida en el cuerpo de la clase, lo que ser implemen-
tado mediante el uso del operador cualificador de mbito (::), que adscribir el cdigo de la definicin a una clase
determinada (en este caso, a ClaseConDatosYMetodos). Ojo: el operador :: permite realizar esto nicamente con
funciones miembros, de forma que el compilador flagelara como error el siguiente cdigo, por el que se intenta
realizar una asignacin a una variable usando tal operador:

ClaseSinMetodos::numeroDNI = 21435019; // ERROR

y la razn es bien clara: las funciones miembros son "servicios"


conceptualmente iguales para todos los objetos de la clase, obviando el
hecho de que operen con datos distintos, mientras que la construccin
anterior sugiere que estamos asignando un valor de una variable a la clase
en s, pero la clase no es un objeto: es slo un ente abstracto. Quiz lo
veamos ms claro en el siguiente ejemplo:

class Persona {
public:
char sexo; // 'M' por masculino, 'F' por femenino
// resto de la descripcin de la clase
};
// la siguiente asignacin es errnea, porque sugiere que
// el tipo abstracto 'Persona' posee siempre sexo masculino,
// lo cual es mentira, a pesar de lo que puedan pensar algunos.
Persona::sexo='M'; // ERROR
// la prxima asignacin es correcta, puesto que establece que
// el objeto Luis de tipo Persona es de sexo masculino.
Persona Luis;
Luis.sexo = 'M';

Puede darse el caso, sin embargo, que deseemos que todos los objetos
compartan una misma variable o constante: entonces deberamos usar un
miembro esttico (static) comn a todos ellos y cuya nocin, como es
costumbre, desarrollaremos ms adelante.

Debemos recordar que estamos trabajando con un lenguaje de jerarqua


dual: las clases son distintas de los objetos, a diferencia de lo que, por
ejemplo, ocurre en el lenguaje SELF.

Bien, superado este fugaz acercamiento, repasaremos seguidamente la


estructura y caractersticas formales de las clases.

C++/OOP: UN ENFOQUE PRCTICO Pgina 77/297


DEFINICIN DE CLASE Y MIEMBROS

De acuerdo a lo anticipado en el epgrafe anterior, las clases pueden


particionarse en cuerpo y cabecera. La cabecera asocia a stas una etiqueta o
especificador de tipo (el nombre de la clase), a la vez que explicita, como
ms adelante veremos, las caractersticas adheridas a las mismas mediante el
mecanismo de derivacin de clases (circunstancia que obviaremos en este
momento), resultando en la siguiente codificacin general:

class Cliente {
// cuerpo de descripcin de la clase
};
Cliente fulano, mengano, zutano; // definicin de objetos
// de tipo "Cliente"

que equivale a:

class Cliente { /* descripcin */ } fulano, mengano, zutano;

El cuerpo de la descripcin de una clase -esto es, su definicin- consta, por


otro lado, de los siguientes elementos formales, encerrados entre llaves
({};):

Notacin C++ Notacin OOP

cualificaciones de acceso exportabilidad de atributos


variables de datos miembros variables de instanciacin
declaraciones de funciones miembros mensajes
definiciones de funciones miembros mtodos

Las funciones y variables (entre las que se incluyen punteros y referencias a


otros objetos de la misma u otras clases) se denominan, respectivamente,
funciones miembros y datos miembros de la clase a la que pertenecen.
Vemoslo en detalle.

DATOS MIEMBROS DE UNA CLASE

Los datos miembros se declaran, dentro del protocolo de descripcin de la


clase, de la misma forma en que en C se declaran las variables: exactamente
igual que si las estuviramos declarando en un tpico struct. Recordemos de
nuevo que una clase equivale a un nuevo tipo definido-por-el-usuario, de
forma que al igual que declaramos variables y punteros de tipos predefinidos
(como char e int), tambin podemos declarar objetos, punteros y referencias
a objetos de otras clases:

C++/OOP: UN ENFOQUE PRCTICO Pgina 78/297


class Familia {
char* domicilioHabitual;
long numeroDePolicia, distritoPostal;
char* ciudad;
Persona cabezaDeFamilia;
Persona conyugeDelCabezaDeFamilia;
Persona* hijos;
// etc., etc.
};

Vemos, as, que se han declarado variables de tipo long y punteros a char,
junto con variables de tipo Persona y punteros a objetos de tipo Persona,
que muy bien pudieran corresponderse a la clase ejemplificada poco antes.

Para declarar un objeto de una clase como miembro de otra clase es


necesario que aqulla haya sido definida. O sea, en nuestro caso: la
declaracin de objetos del tipo Persona en la clase Familia hace necesario que la clase
Persona haya sido anteriormente definida. Esta restriccin se alivia si lo que se declaran, en vez de objetos, son
punteros o referencias a otra u otras clases: en estos casos basta con que se anteceda una declaracin de la
clase en cuestin, pudindose definir ms adelante en el cdigo. Vemoslo en un simple ejemplo:

class Politico; // declaracin de clase


class ComitePara laSalvaguardaNacional // declaracin de clase
{ // comienza la definicin
// definicin de clase
Politico* consejoDeDireccionCorrompido
Politico* directorFiguranteDePaja;
Politico& samaniego, iriarte;
// seguidamente se declara (se "define") como miembro
// de esta clase un "objeto" de una clase todava
// no definida (no terminada de definir),
// cual es la presente clase
Politico hombreHonesto; // ERROR
// es correcta, sin embargo, la declaracin de un puntero
// a la misma clase que se est definiendo, pues sta
// "ya se ha declarado", como vemos a continuacin.
ComiteParaLaSalvaguardaNacional*
comiteDeNuestroPaisVecino;
// ...
}; // aqu termina la definicin de la clase
// ms adelante en el cdigo
class Politico {
// definicin de la clase Politico
};
class EspeciesAutoLucrativas {
// sigue la definicin de la clase
// ahora s se permite la inclusin como miembro
// de esta clase de un objeto de la clase "Poltico",
// pues esta clase ya ha sido definida
Politico cargoXDeLaAdministracion; // OK
// ...
};

C++/OOP: UN ENFOQUE PRCTICO Pgina 79/297


Rizando el rizo -a pesar de que en la prctica a veces es inevitable-, hemos
declarado un puntero incluso a la misma clase que se estaba definiendo. Es
esto correcto? Efectivamente! Se cumplen las condiciones previstas: la
declaracin de la clase es anterior a la declaracin del puntero, y se define
(se termina de definir) ms adelante.

De todas formas el lector se estar preguntando a estas alturas: "pero de


dnde sacar el autor estos ejemplos tan ridculos? Esto no tiene pies ni
cabeza!". Bien, la verdad es que estas exposiciones me resultan bastante ms
costosas que si de "materias prcticas" se tratara: matrices, vectores, listas
enlazadas, cadenas, rboles binarios, etc. constituyen un panorama
ampliamente desarrollado en papel impreso: existen ejemplos,
contraejemplos y recontraejemplos. Pero ocurre algo sorprendente: la
experiencia me ha enseado que el novicio en C++, sobre todo el que
proviene de otros lenguajes clsicos de programacin, rpidamente ve en
estas estructuras una curiosa forma de desarrollar su viejo y conocido
esquema funcional, por lo que, en un abrir y cerrar de ojos, se olvida de las
consideraciones tericas que subyacen bajo los conceptos de clase y objetos
y se limita a modificar levemente su esquema funcional. En definitiva: se
pierde ms de lo que se gana. Indudablemente el lector con experiencia
podra recuperar ms adelante tales conceptos sin demasiados problemas,
pero esto parece ms adecuado para seminarios intensivos o cursos de
especializacin. En lo que a esta introduccin al lenguaje C++ se refiere, tal
y como se estableci en el captulo 1, el enfoque ser siempre el que
proporciona la OOP, ayudndonos, si es posible, de comparaciones
antropomrficas, para que el lector pueda "agarrar" esos conceptos,
inicialmente tan difusos, de encapsulacin, abstraccin, etc. De cualquier
forma tendremos ocasin de revisar, al menos, la clase string y algunas
otras de inters. Claro que esto no es como una guia de una guia del Ulysses
de Joyce, as que retomemos el hilo.

C++/OOP: UN ENFOQUE PRCTICO Pgina 80/297


OCULTACIN DE LOS DATOS

Hemos visto que los datos miembros se han declarado indistintamente como
pblicos o privados en las clases anteriores. En realidad la OOP enfatiza una
propiedad que se denomina ocultacin de la informacin y que,
bsicamente, consiste en que los datos no podrn ser accedidos
directamente, sino que tal acceso ser realizado a travs de funciones
especficas. Qu se pretende con esto? Pues que el usuario (por
desarrollador) no tenga acceso a la representacin interna de la clase.
Imaginemos una clase en la que los datos internos estn dispuestos en
ficheros secuenciales. Si se pudiera acceder sin restriccin a estos datos
directamente, un programador podra generar cdigo basado en la
disposicin fsica secuencial, de forma que si en un momento dado
decidiramos cambiar la disposicin interna de los datos a una lista
enlazada, aqel cdigo debera ser totalmente re-escrito. Solucin?
Mantener los datos como privados e implementar una o varias funciones
pblicas de acceso a los datos, de manera que el programador "ignorar" la
disposicin interna de los datos y manejar nicamente funciones del tipo
buscaClaveCliente(long) y listaClientesEntreCodigos(long, long). Si con tal disposicin deseramos cambiar la
representacin interna de una lista enlazada a una base de datos relacional, efectuaramos los pertinentes
cambios en la definicin de la clase y, por supuesto, en las funciones de acceso, pero el cdigo ya escrito (que
usara nicamente un prototipo inalterable de funcin miembro) no tendra que ser retocado. Bien: sta es la
idea. Para llevarla a buen fin C++ proporciona un sistema de cualificacin de acceso finamente granulado a
travs de etiquetas, como ya hemos podido ver, y que consiste en las siguientes:

public: todo miembro de una clase (funcin o variable) que se


encuentre bajo esta cualificacin de acceso podr ser accedido sin
restriccin desde cualquier punto del programa.

private: un miembro de una clase bajo esta etiqueta nicamente


podr ser accedido por las funciones miembros. Tambin podr ser
accedido por un tipo especial de funciones conocidas como friends
(amigas) y que veremos ms adelante.

protected: este tipo de miembros tendrn la consideracin de


private en lo que a su clase respecta con relacin al programa. Sern,
sin embargo, accesibles desde las clases derivadas de sta, tal y como
veremos ms adelante.

Entonces, perguntar inquietado el lector, para leer, modificar o imprimir


cualquier variable "privada" habr que implementar sendas funciones
especiales adicionales? En efecto! O sea, si tenemos

class Cliente {
private:
char* nombre;
// etc.
};
Cliente miClienteDePrueba;

C++/OOP: UN ENFOQUE PRCTICO Pgina 81/297


para, por ejemplo, imprimir el nombre del cliente, no podramos codificar

cout << miClienteDePrueba.nombre; // error: acceso a


// miembro private

sino que tendramos que definir una funcin "especial" que realizara tal
cometido. As, podramos por ejemplo declarar en la clase Cliente una funcin
imprimerNombre() que realizara la tarea. Vemoslo:

class Cliente {
public:
void imprimeNombre()
{
cout << nombre;
}
private:
char* nombre;
// etc., etc.
};

De esta manera la impresin requerida se producira mediante el siguiente


cdigo:

miClienteDePrueba.imprimeNombre();

Pero bueno! -continuara el lector-, esto quiere decir que aumentaremos el


tamao del cdigo y disminuiremos sus prestaciones en tiempo de
ejecucin, pues lo que en C se realiza de forma directa aqu tiene que
sobrellevar la penalizacin de la llamada a una o ms funciones. De acuerdo:
es cierto, pero slo en una muy corta medida. El aumento del tamao del
cdigo es despreciable en programas no triviales (superiores a mil lneas),
mientras que la performance del cdigo no tiene que verse disminuida si
usamos del mecanismo de inlining. De hecho, como el avispado lector ya
habr observado, la funcin imprimeNombre() ha sido definida dentro del mbito de descripcin de la
clase, por lo que, de acuerdo con lo ya visto, ser automticamente etiquetada como inline, de manera que, al
efectuarse una macro-sustitucin en cada llamada, no se producir penalizacin alguna en run-time. La funcin
en cuestin tambin podra haber sido declarada en la clase y definida fuera del mbito de sta (algo ms acorde
con la filosofa de separacin del interfaz y la implementacin, base de la OOP), pudiendo elegir entonces el
desarrollador si etiquetarla o no como inline. La conveniencia o no de incurrir en la penalizacin antes anunciada
es dejada, sin ms, en manos del usuario del lenguaje.

Por defecto, si no se indica ninguna etiqueta, las clases poseen la


cualificacin de acceso private, reforzando as la idea de ocultacin de la
informacin. De hecho, la nica diferencia en C++ de una class con respecto
a un struct es que este ltimo posee por defecto la cualificacin de acceso
public. Vaya! Entonces, un struct en C++ no es igual a un struct en C? No,
naturalmente: obviando el tema de la cualficacin de acceso por defecto y si
se explicita tal cualificacin mediante etiquetas para cada miembro, donde
quiera que aparezca el trmino class ste puede ser sustituido sin ms por el
de struct. Lo cierto es, sin embargo, que en la comunidad C++ se utiliza

C++/OOP: UN ENFOQUE PRCTICO Pgina 82/297


siempre la class, reservndose el struct prcticamente para lo mismo que en
C.

Consecuencia directa de este esquema de ocultacin es la siguiente premisa:


no se pueden aadir miembros (datos o funciones) a una clase una vez que
sta ha sido definida. Por qu? Bien, resultara absurdo que hubiramos
derrochado esfuerzos en desarrollar el acceso a la representacin interna de
una clase nicamente desde unas determinadas funciones pblicas y que,
por otro parte, cualquier desarrollador, con la simple adicin de una funcin
miembro a la clase ya definida pudiera obtener, sin ms, tal privilegio de
acceso (toda funcin miembro tiene acceso a los miembros private de la
clase a que pertenece). Que se pierde flexibilidad? Est claro. Pero, que
diran ustedes si, por ejemplo, la ley permitiera a los herederos aadir
clusulas al testamento de una persona ya fallecida? Que no es justo?
Bueno, sta es la perenne queja de los posibles herederos y, a la vez, la
carga del testador. Existe en C++, empero, un mecanismo para flexibilizar y
personalizar las clases sin que sea necesario modificar directamente el
cdigo ya escrito: la derivacin de clases, materia de prximas entregas de
esta introduccin.

Existe alguna norma para la colocacin de las etiquetas de acceso? Bien, en


AT&T, por ejemplo, se suele comenzar la definicin de la clase con la
seccin public, seguida de la protected y de la private. Por qu? Pues
porque parece que la exposicin en primer lugar del interfaz pblico, con los
mtodos "de uso" de la clase, puede dar una idea ms precisa del cometido
abstracto de sta, a la vez que explicita ms rpidamente que de otra forma
la necesidad y uso de las variables y funciones private. En otros textos, sin
embargo, el orden es precisamente el inverso: se estima que el
conocimiento inicial de la representacin interna de la clase conllevar una
ms rpida comprensin de los mecanismos que la integran. En esto, como
en tantas otras cosas, dirimir la eleccin personal del lector. En mi caso me
decanto por el primer estilo.

Una ltima nota: la ocultacin de la informacin es una pilar terico de la


OOP y generalmente denota buen diseo, pero no es una caracterstica de
obligatoria aplicacin y, por ende, es posible disear clases vulnerando tal
regla. Pero para esto, como para ejercer la poligamia y el delito, hay que
tener bastante prctica y encima uno siempre acaba cometiendo algn error,
por lo que el lector deber siempre intentar seguir el esquema de ocultacin
de datos, lo que por otro lado le proporcionar grandes satisfacciones en el
futuro.

C++/OOP: UN ENFOQUE PRCTICO Pgina 83/297


FUNCIONES MIEMBROS: VIEJAS SIERRAS CON DIENTES NUEVOS

El conjunto de las funciones miembros de una clase representa,


bsicamente, la expresa delimitacin de las operaciones que un usuario
puede efectuar sobre tal clase. Desde el punto de vista de la OOP, la suma
de estas funciones sera calificada como el conjunto de posibles mensajes a
los que los objetos de tal tipo (por clase) podran responder. En la prctica,
empero, tales funciones podran ser vistas como una restriccin del concepto
general de funcin al mbito de las clases. Veamos un primer ejemplo:

class Persona {
public:
void imprimeDNI(); // funcin sin argumentos
void imprimeNombre();
long leeDNI();
char* leeNombre();
int calculaEdad( Fecha ); // argumento: un objeto
// del tipo Fecha
Ciudad& leeCiudad(); // devuelve una referencia a
// objeto del tipo Ciudad
// ...
private:
long numeroDNI;
Fecha* nacimiento;
char* nombre;
// ...
};

inline void Persona::imprimeNombre()


{
cout << nombre;
}

Vemos que la pura sintaxis funcional es como en ANSI C, con la diferencia


que argumentos y tipos de retorno pueden ser, aparte de los predefinidos,
los correspondientes a tipos definidos-por-el-usuario mediante clases, sin
ninguna distincin visible entre unos y otros: sta es la grandeza de C++.

Existen distintos tipos de funciones miembros? Bien, en realidad s.


Algunos autores basan la particin de estas funciones en grupos atendiendo
principalmente a la finalidad de stas. As Stan Lippman, por ejemplo, las
clasifica en funciones de gestin, de implementacin, de ayuda y de acceso,
aunque avisa que no existe diferencia fsica entre tales grupos,
establecindose stos con fines preminentemente didcticos, sin valor formal
efectivo. En lo que a nosotros respecta consideraremos nicamente tres
grupos: en primer lugar el formado por un tipo especial de funciones
miembros llamadas contructores y destructores; seguidamente, el
constituido por los operadores, bien de conversin bien por sobrecarga; en
el ltimo grupo, finalmente, se despacharan todas las dems funciones.
Tendremos ocasin de revisar en ms detalle tales grupos.

C++/OOP: UN ENFOQUE PRCTICO Pgina 84/297


EL PUNTERO IMPLCITO "THIS"

Volvamos a sumergirnos en las clases y en la conceptualizacin dual


clase-instanciacin/tipo-objeto. Imaginemos que hemos dotado a una clase,
convertida ya en un verdadero tipo definido-por-el-usuario, de una
representacin interna sustentada en variables (presumiblemente privates),
de tal forma que esta "estructura" sera traspasada a cada uno de los objetos
de tal tipo. Cada objeto, seguidamente, podra inicializar y "rellenar" de
forma conveniente tal "estructura". O sea, un nmero 'n' de objetos supone
un igual nmero 'n' de representacin internas: cada objeto dispone de un
rea de almacenamiento propia -no compartida por los dems objetos en el
caso ms general- dedicada a soportar tal representacin. Qu ocurre, sin
embargo, con las funciones miembros? Cada objeto repite, como ocurre
con las variables, todas las funciones definidas en la clase de que toma el
tipo? No, por supuesto! Repetir el cdigo de la funcin para cada uno de los
objetos constituira una prdida de espacio de muy difcil justificacin. En
realidad todos los objetos de una clase usan de la misma nica copia de
cada una de las funciones. Esto es: todos los objetos comparten el mismo
cdigo para cada funcin miembro. Pero, entonces, si suponemos que una
determinada funcin miembro, mediante la general codificacin en el mbito
de definicin de la clase, tiene que acceder a la representacin interna de un
objeto, cmo podr discernir tal funcin, basndose en un mismo cdigo
comn, el objeto con que deber operar para tratar con sus variables
privadas? Intentar clarificar el problema con el siguiente ejemplo:

class Proveedor {
public:
void imprimeNombre();
void imprimeDireccion();
void imprimeFichaProveedor();
// ...
private:
char* nombre;
char* direccion;
// ...
};

void Proveedor::imprimeNombre()
{
cout << nombre << "\n";
}

void Proveedor::imprimeDireccion()
{
cout << direccion << "\n";
}

void Proveedor::imprimeFichaProveedor()
{
imprimeNombre();

C++/OOP: UN ENFOQUE PRCTICO Pgina 85/297


imprimeDireccion();
}

Veamos si el lector ha entendido la naturaleza del problema: es evidente que


si declaramos un objeto como del tipo Proveedor y seguidamente le enviamos el mensaje
imprimeNombre,

Proveedor proveedorDescargandoEnElMuelle;
proveedorDescargandoEnElMuelle.imprimeNombre();

la funcin miembro imprimeNombre() conocer en este caso con exactitud a qu objeto ha de ser
aplicada, evidentemente. Asi mismo, cuando codificamos

proveedorDescargandoEnElMuelle.imprimeFichaProveedor();

este nuevo mensaje o funcin miembro tambin sabr que el objeto a que
debe aplicarse, al igual que en el ejemplo anterior, es el proveedor-
DescargandoEnElMuelle. Pero, qu pasa con las funciones miembros que se encuentran, sin referencia directa
alguna, en el cuerpo de esta ltima funcin? O sea, a qu objeto se aplicarn los mensajes imprimeNombre() e
imprimeDireccion(), contenidos en la funcin imprimeFichaProveedor(), toda vez que la sintaxis no es explcita
como en los casos anteriores? Bien, en realidad todas las llamadas a miembros de una clase (tanto variables como
fuinciones) desde el protocolo de descripcin de sta se direccionan a un puntero implcito, denominado this, que
contiene la direccin del objeto de esa clase por medio del que se ha producido la llamada. Esto es, si explicitamos
tal puntero, podramos perfectamente escribir:

void Proveedor::imprimeNombre()
{
cout << this->nombre << "\n";
}

void Proveedor::imprimeFichaProveedor()
{
this->imprimeNombre();
this->imprimeDireccion();
}

donde this, en este caso, habra sido ya implcitamente declarado de la


siguiente forma:

Proveedor *const this;

De esta forma, en nuestro caso, ya podemos inferir que los mensajes del
cuerpo de la funcin imprimeFichaProveedor() sern direccionados al objeto
proveedorDescargandoEnElMuelle, como era intuitivo suponer. Naturalmente que el usuario puede explicitar el
puntero this en los cuerpos de todas las funciones miembros, aunque esto, lejos de aclarar, oscurece ms el
cdigo y, en la prctica, simplemente no se hace.

Hemos visto que this es un puntero constante a un objeto de la clase a que


pertenece el miembro, de tal forma que this no puede ser cambiado, aunque
s puede serlo el objeto apuntado por l, o sea *this. Podemos, pues, usar
sin restricciones esta nueva caracterstica, como por ejemplo en el valor de

C++/OOP: UN ENFOQUE PRCTICO Pgina 86/297


retorno de las funciones miembros. Vamos, en base a esto, a revisar y
reescribir la clase antes propuesta:

class Proveedor {
public:
Proveedor imprimeNombre();
Proveedor imprimeDireccion();
void imprimeFichaProveedor();
// ...
private:
char* nombre;
char* direccion;
// ...
};

Proveedor Proveedor::imprimeNombre()
{
cout << nombre << "\n";
return *this;
}

Proveedor Proveedor::imprimeDireccion()
{
cout << direccion << "\n";
return *this;
}

void Proveedor::imprimeFichaProveedor()
{
imprimeNombre().imprimeDireccion();
}

Qu hemos cambiado? Bien, hemos convertido imprimeNombre() e imprimeDireccion() en


funciones con valor de retorno del tipo Proveedor y modificado convenientemente sus cuerpos. De esta manera
si se produce el envo de uno de tales mensajes a un objeto determinado, como por ejemplo
proveedorDescargandoEnElMuelle.imprimeNombre(), el resultado de tal envo ser la impresin del nombre y el
retorno del mismo objeto proveedorDescargandoEnElMuelle (*this). Examinemos ahora la definicin de la funcin
imprimeFichaProveedor(). Hemos encadenado las dos funciones anteriores con el siguiente significado: si
suponemos la llamada proveedorDescargandoEnElMuelle.imprimeFichaProveedor(), primero se ejecutar el bloque
ms hacia la izquierda, equivalente a this->imprimeNombre(), que en este caso equivale, a su vez, a
proveedorDescargandoEnElMuelle.imprimeNombre(), que tras ejecutarse devolver el objeto
proveedorDescargandoEnElMuelle, al que seguidamente se aplicar el siguiente bloque a la izquierda, resultando
en proveedorDescargandoEnElMuelle.imprimeDireccion(), que a su vez devolvera el mismo objeto al que podra
aplicarse otro bloque, si lo hubiere.

C++/OOP: UN ENFOQUE PRCTICO Pgina 87/297


FUNCIONES MIEMBROS CONSTANTES

Recordemos que, bsicamente, un objeto consta de una representacin


interna y de un interfaz de mtodos que acceden, manipulan y modifican tal
representacin. Pensemos, por otro lado, que la definicin de una clase
posibilita su uso en calidad de nuevo tipo definido-por-el-usuario, con
sintaxis pareja a la de los tipos predefinidos. Una variable de un tipo
incorporado puede declararse sin problemas como constante, pero qu
pasa con los objetos de una clase?

Si declaramos un objeto de una clase dada como constante e intentamos


enviarle un mensaje en razn de una funcin miembro de tal clase que
modifique la representacin interna del objeto, el compilador sealar
inmediatamente el error. En realidad a un tal objeto nicamente podran
serle aplicadas funciones miembros que mantuvieran invariante su estructura
interna. Pero, cmo ha de saber el compilador que tal o cual funcin es
"segura" en este sentido? Pues merced a que el desarrollador habr
etiquetado tal funcin miembro como constante, tanto en su declaracin
como en su definicin. As, por ejemplo, podramos reescribir alguna de las
anteriores funciones de la siguiente forma:

class Proveedor{
public:
Proveedor imprimeNombre() const;
// ...
};

Proveedor Proveedor::imprimeNombre() const


{
cout << nombre << "\n";
return *this;
}

Por supuesto slo podemos etiquetar como constantes las funciones


miembros que no modifiquen la representacin interna de los objetos (los
datos miembros), obteniendo un error en compilacin en caso contrario.

Si declaramos un objeto de una determinada clase como constante, a tal


objeto slo podrn serle dirigidos mensajes significados por funciones
miembros constantes. Una funcin miembro constante podr, sin embargo,
ser llamada desde cualquier objeto, constante o no. O sea, que tenemos:

const Proveedor proveedorConstante;


Proveedor proveedorDeAccesorios; // ok
// seguidamente objeto constante llama a funcin constante
proveedorConstante.imprimeNombre(); // ok
// objeto constante llama a funcin no const
proveedorConstante.imprimeDireccion(); // error
// ahora objeto no constante llama a funcin const
proveedorDeAccesorios.imprimeNombre(); // ok

C++/OOP: UN ENFOQUE PRCTICO Pgina 88/297


En una funcin miembro declarada como constante, como por ejemplo la ya
vista imprimeNombre(), el puntero this queda implcitamente declarado como

const Proveedor* const this;

Todo lo visto hasta ahora parece que viene en afirmar que una funcin
miembro declarada constante no puede modificar un objeto, pero lo cierto
es que s puede. Demonios!, pensar el lector: Ahora s que no se entiende
nada!. Bueno, como tantas veces suele ocurrir en C++, el lenguaje
proporciona fuertes mecanismos por defecto que sin embargo pueden ser
totalmente obviados por el desarrollador: pensemos, por ejemplo, en el
"fuerte chequeo de tipos", que puede ser totalmente anulado mediante la
tcnica llamada de "constructores virtuales". Bien, volviendo a las funciones
const, debo decir que esta etiqueta se refiere a lo que se denomina
"constancia lgica", en contraposicin a la constancia fsica, indicando que tal
caracterstica es, en definitiva, una mera apariencia, que el usuario puede
soslayar simplemente realizando un cast expreso de la siguiente forma:

void Proveedor::cambiaNombre( char* nombreNuevo ) const


{
(Proveedor*)this->nombre = nombreNuevo;
}

Esto es, en C++ normalmente el deseo expreso del desarrollador prima


sobre todo lo dems: algo que suscita odios y pasiones.

INTERFAZ E IMPLEMENTACIN

Como el lector ya habr adivinado, las definiciones de las clases,


constitutivas del interfaz del proyecto software, se registran en archivos de
cabecera (de extensin .h, .hxx o .hpp), mientras que las definiciones de las
funciones miembros, o implementacin, sern encerradas en archivos con
extensin .CPP .C. En el protocolo de descripcin de las clases no deben
caber definiciones, sino slo declaraciones. Cuando ms adelante
expongamos ejemplos completos de anlisis, diseo y codificacin de clases
comentaremos en detalle esta lmpida separacin.

C++/OOP: UN ENFOQUE PRCTICO Pgina 89/297


ENTRADA Y SALIDA
6
DE DATOS

Debido a la ansiedad (como relata Bertrand Meyer) que experimentan los


novicios respecto de las operaciones de entrada y salida de datos, antes de
seguir desarrollando las caractersticas generales de las "clases" vamos a
practicar un sucinto repaso a la librera standard de i/o en C++.

INTRODUCCIN A LA LIBRERA "IOSTREAM.H"

La librera iostream es el nico soporte estndar actual, segn las


especificaciones de AT&T, incorporado al lenguaje C++ para la gestin de
las operaciones de entrada/salida de datos. Originariamente tal librera,
implementada y diseada por Bjarne Stroustrup, se denomin stream. A
partir, sin embargo, de AT&T C++ 2.0 sta fue reimplementada por Jerry
Schwarz, con la significativa adicin de los manipuladores y adoptando su
actual nombre, constituyendo la base para el establecimiento de la librra
i/o en ANSI C++ que, en todo caso, ser una simplificacin de la presente.

En esta librera, incluida en el archivo de cabecera iostream.h, se predefinen


los siguientes cuatro objetos bsicos, instancias de las clases ostream (salida: output
stream) e istream (entrada: input stream).

cin (ledo see-in), que es un objeto predefinido de clase istream afectado a


la entrada standard.

cout (ledo see-out), que es un objeto predefinido de clase ostream


direccionado al dispositivo standard de salida.

cerr y clog, objetos predefinidos de clase ostream para el tratamiento de salida


mensajes de error, sin y con buffer respectivamente.

Un momento, un momento! De dnde han salido estos objetos? Quin,


cmo han sido inicializados? Y qu ocurre con ellos tras terminar la
ejecucin de nuestro programa? Bien, son buenas preguntas. Las
respuestas? Concisamente: tales objetos globales son declarados static en el

C++/OOP: UN ENFOQUE PRCTICO Pgina 90/297


archivo de cabecera iostream.h, y resulta que las funciones constructoras de
los objetos estticos (locales o globales) son llamadas antes de la ejecucin
de main(), as como las destructoras son llamadas despus de acabar
main(). Vaya! Y por qu? Pues porque tal esquema permite, como en este
caso, unas adecuadas y elegantes inicializacin y destruccin de estructuras
de datos en libreras, en su forma ms general. Y por qu as y no de otra
manera? Porque las dems alternativas son peores o demasiado
especializadas para cada librera. Y ya est bien de preguntar. Sigamos.

En las clases istream y ostream se sobrecargan11, respectivamente, los operadores para la ejecucin de las
operaciones de insercin o entrada de datos (<<) y extraccin (>>) o salida de datos. De esta forma la
representacin codificada de I/O podra ser:

cout << datos; // insercin de datos en el


// objeto o "stream" cout
cin >> datos; // extraccin de datos del
// objeto o "stream" cin

Veamos seguidamente el tpico programa de saludo:

#include <iostream.h>
int main( int, char** ) {
cout << "Hola, C++";
return 0;
}

Notamos, pues, que la cadena "Hola, C++" se inserta en el objeto cout,


representacin virtual del dispositivo de salida (normalmente la pantalla del
terminal).

Bueno, pero por qu utilizar los operadores '<<' y '>>' en lugar de otros?
Y por qu, en todo caso, usar dos operadores distintos cuando la distincin
en s de las operaciones fcilmente se localiza en los objetos predefinidos?
En primer lugar tenemos que no podemos "crear" nuevos operadores:
tenemos que conformarnos con los existentes. Seguidamente, en efecto,
cabra preguntarse: cul? quiz el operador '='? Lo cierto es que las
expresiones (sobre todo al encadenarse) pueden complicarse sobremanera, y
una no muy intuitiva adecuacin del operador a la operacin dada podra
dificultar, a veces de forma insuperable, la legibilidad del cdigo. Y es tal
inteligibilidad la que se pretende al elegir dos operadores asimtricos que
por su forma ya sugieren un direccionamiento (de datos, de objetos, etc.).
Realice el amable lector una sencilla prueba: tras terminar el presente

11
Recuerde el lector lo ya dicho sobre sobrecarga de funciones (de las que los
operadores son un caso especial): el mismo interfaz con distintas implementaciones
contextuales. De esta manera los operadores normalmente usados para
desplazamiento de bits son "redefinidos" para actuar como "insertores" y
"extractores" en sus relaciones con streams.

C++/OOP: UN ENFOQUE PRCTICO Pgina 91/297


captulo cjase la tabla de operadores y sustituya los operadores '<<' y '>>'
por los de su eleccin en las expresiones de este texto. Colija el lector,
seguidamente, la adecuacin o no de tal eleccin12.

Cualquiera de los operadores puede ser utilizado en forma encadenada. Esto


es,

cout << "El nmero " << 7 << " es cabalstico." << "\n";
// la siguiente lnea es equivalente a la anterior
cout << "El nmero 7 es cabalstico\n";

debido a la definicin del operador sobrecargado, que devuelve, como


veremos, una referencia a ostream:

ostream& operator<<( tipoDeDato );

De esta forma, la operacin de "insercin" de un dato en el objeto cout devuelve


el mismo objeto, preparado para una posible nueva "insercin" de datos. El cdigo anterior podra, pues, ser visto
as:

( ( ( ( cout << "El nmero" ) << 7 )


<< "es cabalstico." )
<< "\n"; )

La idea intuitiva del "funcionamiento" del objeto cout, por ejemplo, es que
los datos se van insertando en un buffer interno que una vez lleno, o forzado
por un manipulador o una funcin miembro, direcciona su volcado hacia un
dispositivo de salida preestablecido, que por defecto (igual para entrada que
para salida) es la pantalla del terminal, aunque, como es fcil suponer, tal
circunstancia puede ser variada direccionando el volcado a otro dispositivo.

Es muy aconsejable el estudio por los lectores de los archivos de cabecera de


las clases stream en su implementacin C++, pues son stas libreras muy
chequeadas con una ortodoxa y cuidada implementacin de la derivacin de
clases y en las que se da una inteligente y particionada (en el sentido
matemtico del trmino) profusin de funciones miembro.

12
No olvidemos que C++ es un lenguaje que permite al desarrollador una gran
flexibilidad: el lector disconforme con la eleccin de operadores podra, por ejemplo,
derivar sus propias clases de istream y ostream aadiendo a stas, seguidamente
y tras lo que puede ser un arduo trabajo, un operador distinto que, caracterizado
como inline, sustituyera a aqullos, creando a la vez unos nuevos streams micout
y micin. Tal posibilidad existe, como tambin la de automutilarnos, pero esto no
dice nada de la conveniencia de su aplicacin.

C++/OOP: UN ENFOQUE PRCTICO Pgina 92/297


MENSAJES DE I/O DIRIGIDOS A OBJETOS "STREAMS"

Hemos visto que la simple codificacin:

cout << 8;
cout << "hola";

origina que se impriman sucesivamente en la consola un entero y una


cadena. Pero, cmo demonios sabe el objeto cout que el primero es un entero y el
segundo no? O sea, que pasa con la conocida necesidad de indicar el tipo del "dato" a imprimir?, algo tan
habitual en 'C' como lo siguiente:

printf ( "He aqu un entero: %d \n", 8 );

La respuesta, a estas alturas, ya debe haberla adivinado el lector: los


operadores de insercin y extraccin son sobrecargados en las clases istream
y ostream, de forma que el mismo operador (esto es, el mismo identificador
de funcin, sea '<<' '>>') admite distintos argumentos, producindose en
cada caso una llamada a la funcin especfica con argumento(s) del mismo
tipo que el objeto que se pretende insertar o extraer de un stream.

Examinndolo desde una ptica Orientada-a-Objetos, esta situacin sugiere


la siguiente interpretacin: un objeto determinado (un entero, una cadena,
etc.) dirige un mensaje de extraccin (>>) o insercin (<<) al objeto
stream, el cual, a su vez, procede a la siguiente evaluacin general:

ORIENTADA-A-OBJETOS FUNCIONAL

qu objeto enva el mensaje? cul es el tipo del objeto que


enva el mensaje?
existe un mtodo de respuesta
para tal mensaje? se ha definido en la clase a
que pertenece el stream una
funcin con un argumento del
mismo tipo que el objeto que
enva el mensaje?
En caso que no: existe una
funcin miembro con un ar-
gumento de un tipo al que el
tipo del objeto que enva el
mensaje pueda convertirse por
la aplicacin de las reglas de
resolucin en sobrecargas de
funciones?
El objeto stream enva un
mensaje de respuesta. Se produce una llamada a la
funcin resultante de la eva-
luacin anterior.

C++/OOP: UN ENFOQUE PRCTICO Pgina 93/297


Llegamos, as, a la facilidad de que sea el sistema el que se ocupe del
trabajo sucio, preocupndose de discernir la respuesta a aplicar en cada
momento en razn de las caractersticas concretas del estmulo. De esta
manera, por ejemplo, si declaramos una variable del tipo int y usamos de
ella en expresiones con streams, el cambio del tipo de la variable a,
verbigracia, char bsicamente no nos habr de afectar.

Vemos, pues, que los objetos predefinidos pueden manejar un elevado


rango de tipos, pues en definitiva estn provistos de funciones miembro que
implementan mtodos de respuesta a los mensajes (singularmente << y
>>) que les envan objetos de distintos tipos (int, double, char*, etc.). Esto
obedece a una codificacin de la siguiente catadura:

class ostream : public virtual ios {


public:
ostream& operator<<( const char* );
ostream& operator<<( char );
ostream& operator<<( short i ) {
return *this << int(i); // inline
}
ostream& operator<<( double );
// etc., etc.
};

Tal versatilidad puede generar alguna confusin como, por ejemplo, en el


caso de los punteros a char, pues este tipo de dato ser asumido por el
objeto iostream como una cadena tipo C, as que si quisiramos usar la
direccin contenida en tal puntero tendramos que realizar un cast explcito
de la forma

char* punteroAChar = "Prueba objeto cout";


cout << punteroAChar; // cadena apuntada por puntero
cout << ( void* )punteroAChar; // direccin puntero

Ahora lo entiendo! -podra suspirar aqu el paciente lector-: entonces si, por
ejemplo, deseara imprimir un objeto de un tipo definido-por-el-usuario a
travs de una clase, lo nico que tendra que hacer es descomponer el
objeto en datos que seran insertados individualmente en el objeto cout. O no es
as? Bien, bsicamente s es as. De esta manera podramos codificar:

class Recluta {
public:
void imprimeDatos()
{ // funcin inline
cout << "Datos recluta Ejercito de Tierra:\n";
cout << numeroDNI << endl;
cout << apodo << "\n";
}
private:
long numeroDNI;

C++/OOP: UN ENFOQUE PRCTICO Pgina 1/297


char* apodo;
};

siendo as que si definimos e inicializamos un objeto del tipo Recluta, como


por ejemplo miRecluta, para imprimir los datos internos deberamos
codificar

miRecluta.imprimeDatos();

Entonces, esto es correcto? es as como se imprimen los objetos


instanciaciones de clases?. Bien, la verdad es que no. Estamos tan
acostumbrados a pensar siguiendo un esquema funcional que enseguida
queremos componer funciones "al antiguo estilo" con todo lo que tenemos a
nuestro alcance. Hay que cuestionarse lo siguiente: realmente C++ hace un
gran esfuerzo para equiparar los tipos predefinidos con los definidos-por-el-
-usuario a travs de la clase, mediante parecida sintaxis de declaracin y
definicin, encapsulando las operaciones, etc., y sin embargo nosotros
obviamos esta evidencia y decimos: "bien, los objetos de tipo predefinido
con los operadores de insercin y extraccin, mientras que las
instanciaciones de clases con funciones especficas". No hay razn para
establecer tal distincin: en efecto, la impresin del objeto miRecluta, de tipo
Recluta, debera ser codificada as:

cout << miRecluta; // !!!!!

Pero -dirn ustedes-, se supone que la clase ostream no posee una funcin
miembro con argumento del tipo Recluta, pues el implementador de tal clase
no saba -ni afortunadamente sabr nunca- nada sobre esta nuestra clase.
Qu pasa aqu? Bien, es muy sencillo: nicamente tenemos que
sobrecargar, en la clase Recluta, los operadores de insercin y extraccin,
para que respondan a argumentos del tipo Recluta. Qu cmo se hace
esto? Ms adelante lo veremos.

De hecho se encuentran con frecuencia en los textos de introduccin a C++


ejemplos de sobrecarga de los operadores de insercin y extraccin en el
mbito de una clase, pues tales se han asimilado fuertemente a las
operaciones de entrada y salida. As, verbigracia, estos operadores suelen
sobrecargarse como indicadores de las operaciones de archivo y
recuperacin de objetos almacenados en "objetos ficheros", etc.

Vemos, en definitiva, que la flexibilidad en la aplicacin de las operaciones


de insercin y extraccin es poco menos que ilimitada.

FUNCIONES MIEMBROS DE LOS STREAMS

Dadas las caractersticas de los streams, podra ser aconsejable la inclusin


en un archivo de cabecera de constantes simblicas para los items ms
usados, verbigracia:

C++/OOP: UN ENFOQUE PRCTICO Pgina 2/297


const char campana = '\007';
const char* flecha = " ===> ";

pudiendo ser usados de la siguiente forma:

cout << "La respuesta es " << flecha << "NO" << campana << endl;

donde endl es una operacin predefinida en ostream que inserta una nueva
lnea y, a la vez, vaca el buffer. A este ltimo y nico fin tambin podran
utilizarse las siguientes expresiones:

// fuerza el volcado del buffer


cout << flush; // mediante un "manipulador"
cin.flush(); // mediante una funcin miembro

En realidad los streams predefinidos poseen gran cantidad de funciones


miembros para el manejo de datos, entre las que cabra destacar:

cout

// inserta carcter en cout


cout.put( char caracter );
// inserta desde cadena[0] hasta cadena[longitud] en cout
cout.write( const char* cadena, int longitud );
// vaca el buffer en el dispositivo de salida
cout.flush();
// ...

cin

// extrae un carcter del objeto cin


cin.get( char& caracter );
// iterador extractor que devuelve un caracter
cin.get();
// extrae lnea desde buffer[0] a buffer[limite]
// o hasta encontrarse con el delimitador indicado
cin.getline( char* buffer, int limite,
char delimitador = '\n' );
// extrae una cadena desde direccion[0]
// hasta direccion[longitud]
cin.read( char* direccion, int longitud );
// devuelve nmero de caracteres ledos
// en el ltimo cin.read( ... )
cin.gcount();
// devuelve a cin un carcter extrado
cin.putback( char caracter );
// desecha la extraccin del buffer
// desde buffer[limite] hasta el delimitador
cin.ignore( int limite, int delimitador = EOF );
// devuelve el siguiente carcter sin extraerlo
cin.peek();
// ...

C++/OOP: UN ENFOQUE PRCTICO Pgina 3/297


MANIPULADORES

En el apartado anterior hemos visto que se puede forzar el volcado del


buffer de un stream bien mediante una funcin miembro bien a travs de un
manipulador. Qu es, sin embargo, un manipulador? En pocas palabras: es
un objeto (un objeto "funcin", por as decirlo) que cada vez que se utiliza
equivale a una llamada a una determinada funcin miembro. As, como ya
hemos visto,

cout << flush;

equivale a

cout.flush();

O sea, la insercin del objeto flush en el objeto cout equivale a (o provoca) la llamada de la funcin
miembro flush() en este ltimo.

Esta tcnica, ideada por Andrew Koenig, permite una sintaxis ms clara y
una mayor flexibilidad, pues las operaciones de entrada y salida no tienen
que ser interrumpidas para la aplicacin de determinadas funciones.

Los manipuladores con argumentos (?!) estn definidos en el archivo "ioma-


nip.h" y son:

setw( int ) // establece la longitud del


// buffer del campo de datos
setfill( int ) // establece el carcter de
// relleno de campos de datos
setbase( int ) // establece la base numrica
// de conversin
setiosflags( long ) // establece las marcas en el
// vector de bits de control
resetiosflags( long) // limpia todas las marcas
// del vector de bits de control
setprecision( int ) // establece los digitos de precisin

La lista, por otro lado, de manipuladores sin argumentos est compuesta


por:

oct // determina base numrica octal


dec // determina base numrica decimal
hex // determina base numrica hexadecimal
flush // fuerza el volcado del buffer
endl // equivale a la aplicacin
// consecutiva de '\n' y flush
ends // equivale a la adicin de '\n'
// y la aplicacin de flush
ws // se "come" (u obvia) un espacio en blanco

C++/OOP: UN ENFOQUE PRCTICO Pgina 4/297


Lo cierto es que al lector no le habr quedado muy clara la tcnica en que se
base el funcionamiento de los manipuladores, pero una explicacin ms
detallada sobrepasara los lmites de esta introduccin. Bstele saber que los
manipuladores son extensibles (particularizables) y tambin que el
desarrollador puede crear sus propios manipuladores. A estas alturas es una
cuestin de fe.

FORMATO DE LOS DATOS DE ENTRADA/SALIDA

Hemos visto que ciertos manipuladores (como por ejemplo hex y setprecision(int))
pueden afectar el formato de las operaciones de entrada/salida. De hecho los manipuladores sin argumentos se
definen en la clase ios, que es una clase base (superclase) de las clases istream y ostream. Esta clase dispone de
distintas funciones miembros que pueden ser aplicadas a los stream predefinidos. Alto aqu! Un objeto va a usar
funciones miembros de otra clase como si fueran suyas? Efectivamente! Mediante la derivacin de clases, los
objetos de las clases derivadas pblicamente de una clase base (cual es ios en este caso) pueden llamar
directamente a las funciones miembros declaradas en la seccin public de tal clase base. Bien, veamos algunas de
tales funciones, aplicables tanto a objetos ostream como istream:

cout.fill( char ); // establece el carcter de relleno


// ( por defecto espacio)
cout.fill(); // devuelve el carcter de relleno
cout.width( int ); // establece la longitud del
// campo de salida
cout.width(); // devuelve la longitud del
// campo de salida
cout.precision( int ); // establece la precisin en decimales
cout.precision(); // devuelve la precisin.
cin.eof(); // devuelve la condicin booleana
// de fin de fichero
// ...

Los objetos streams mantienen, adicionalmente, un estado interno de


formato para controlar las operaciones de formato a travs de seales (flags)
especficas. Tales seales pueden ser activadas o desactivadas mediante las
funciones miembros de la clase ios setf(...) y unsetf(...), respectivamente. El establecimiento de
un formato de salida podra ser codificado, en general, de la siguiente forma:

cout.setf( ios::FLAG );

donde FLAG habr de ser sustituido por uno de los siguientes identificadores:

left // justificacin a la izquierda


right // justificacin a la derecha
internal // justificacin entre signo y nmero
dec // salida numrica en base decimal
hex // salida numrica en base hexadecimal
oct // salida numrica en base octal
fixed // salida de nmeros con coma flotante
// en notacin regular
scientific // nmeros con coma flotante

C++/OOP: UN ENFOQUE PRCTICO Pgina 5/297


// en notacin cientfica
showpos // antecede un signo '+'
// a los nmeros positivos
showpoint // muestra el punto decimal y los ceros
// tras ste en nmeros decimales
uppercase // salida de caracteres en maysculas
skipws // salta espacio en blanco en entrada de datos

Existe, a la vez, la siguiente sobrecarga de la funcin setf ( long ):

long setf( long flagOCampoDeBitsDeFormato,


long campoDeBitsDeFormato );

donde el primer argumento puede ser una flag de las antes detalladas o un
campo de los siguientes,

basefield // base numrica


floatfield // notacin de nmeros con coma flotante

de manera que cada campo (Format Bit Field) se aplica a unas determinadas
seales (Format Flags). As

ios::basefield se aplica a las flags -> ios::hex,


ios::dec,
ios::oct
ios::floatfield se aplica a las flags -> ios:fixed,
ios::scientific

Pero, vaya! para qu es necesaria realmente esta ltima sobrecarga? Bien,


resulta que la codificacin setf( FLAG ) resulta en que se activa una determinada format flag, pero
sin inicializar el estado del campo a que se adscribe. Esto es, si escribimos

int numero = 10;


cout.setf( ios::hex ); // establece 'basefield'
// como hexadecimal
cout << numero; // salida: A
cout.setf( ios::oct) ; // no resetea el campo 'basefield',
// estableciendo dos bases distintas
cout << numero; // salida: 10 !!! (la base no es octal)

tenemos que al aplicar dos distintas bases sobre el campo ios::basefield, ste vuelve
a su valor por defecto: la base decimal. Cmo se soluciona esto? Bien, la versin sobrecargada primero resetea
el campo de formato a 0 y seguidamente establece en l el valor del flag. Y si quiramos conservar el antiguo
valor del campo? Precisamente long setf( long, long ) devuelve tal valor, por lo que podr ser almacenado y
recuperado posteriormente.

UN SENCILLO EJEMPLO PRCTICO

C++/OOP: UN ENFOQUE PRCTICO Pgina 6/297


Veamos algunas lneas de cdigo mostrando el manejo elemental -muy
elemental- de la librera "iostream.h", haciendo uso de funciones miembros
y de manipuladores:

#include <iostream.h>
int main( int, char** )
{
// inserciones // salida a dispositivo estndar
// y comentarios
cout << hex << 10; // A
cout << oct << 9; // 11
cout << oct << 9
<< hex << 10; // 11 A
cout << char( 67 ); // 'C' en un PC
cout << 'C'; // C
cout << int( 'C' ); // 67 en un PC ( CHAR es distinto
// de INT en C++)
cout.precision( 2 ); // establece la precisin SLO
// para la prxima insercin
cout << 1.23234; // 1.23
cout << 1.23234; // 1.23234
cout.width( 5 ); // establece la longitud de
// la prxima insercin
cout << 2; // 2
cout << 2; // 2
cout .width( 8 ); // establece la longitud SLO
// de la prxima insercin
cout.fill( '0' ); // establece el carcter de
// relleno para completar "width"
cout.precision( 3 );
cout << 1.28317; // 0001.283
cout.width( 9 );
cout << 1.28317; // 001.28317
cout.fill( ' ' ); // restablece el caracter de
// relleno a Blanco
return 0;
}

Observamos que algunos mtodos (como width(int) precision(int)) se aplican nicamente en


la insercin de datos en el objeto cout inmediatamente posterior a su llamada, restaurndose, tras sta, los
valores anteriores. Otros, empero, (como fill(char)) cambian de forma duradera el comportamiento del objeto
cout.

MANEJO DE FICHEROS

Las clases para el manejo de operaciones de entrada/salida en ficheros estn


contenidas en el archivo "fstream.h", el cual automticamente incluir -si
no ha sido hecho ya- el archivo "iostream.h". Tales clases, a nuestros
efectos, se reducen a las siguientes:

. ifstream: para ficheros que se abren en modo lectura

C++/OOP: UN ENFOQUE PRCTICO Pgina 7/297


. ofstream: para ficheros que se abren en modo escritura
. fstream: para ficheros abiertos en modo lectura/escritura

Entonces, cul es el funcionamiento prctico de este esquema? En primer


lugar definimos un "objeto fichero" del tipo apropiado [i][o]fstream para
despus proceder a su apertura, como por ejemplo

ofstream miFicheroEnModoEscritura;
miFicheroEnModoEscritura.open( "fichero.txt", ios::out );

O en un solo paso:

ofstream miFicheroEnModoEscritura( "fichero.txt", ios::out );

donde el primer argumento representa el nombre del fichero a abrir,


mientras que el segundo indica el "modo" de apertura, de tal forma que en
caso de un objeto ofstream podra ser ios::out (output mode) ios::app
(append mode), mientras que en el caso de un objeto ifstream sera ios::in
(input mode); un objeto de tipo fstream podra aplicar cualquiera de los
modos13. Tras las operaciones con el objeto fichero ste se cerrara mediante
la aplicacin de la correspondiente funcin miembro

miFicheroEnModoEscritura.close();

En realidad los objetos [i][o]fstream cuentan con variedad de funciones


miembros, entre las que podramos destacar:

fstream miFichero;
// apertura de un fichero (los modos se pueden disyuntar)
miFichero.open( "fichero.txt", ios::in | ios::out );
// coloca el puntero en una posicin determinada del fichero,
// donde POS sera sustituido por beg (inicio: POR DEFECTO),
// cur (posicin actual) o end (final fichero)
miFichero.seekg( long posicion, ios::POS );
// devuelve la posicin actual en un fichero
// desde el inicio del mismo
miFichero.tellg();
// cierra fichero
miFichero.close();

Por supuesto los objetos [i][o]fstream tienen acceso a las funciones


miembros de los streams detalladas en pargrafos anteriores, a efectos del
uso de formateadores de entrada/salida, etc. Naturalmente, tambin, los

13
En realidad la funcin miembro "open" consta de un tercer argumento, que
podramos calificar como de acceso, y que mantiene una correspondencia con los
conocidos atributos de los ficheros: normal, read-only, hidden y system. Por
defecto tal argumento est establecido en la posicin de "normal".

C++/OOP: UN ENFOQUE PRCTICO Pgina 8/297


operadores '<<' y '>>' han sido sobrecargados para su uso por objeto
ficheros, de tal forma que el cdigo

ofstream miFichero( "clientes.txt", ios::app );


miFichero << "Piratas Informticos del Mediterrneo S.L.";

responde a la interpretacin: insercin de un objeto "cadena" en un objeto


ofstream. Como veamos con los streams, no debemos preocuparnos de
sealar expresamente el tipo del objeto a ser insertado o extrado.

De la misma forma que ocurre con los ficheros, en el fichero "strstream.h"


se pueden encontrar distintas clases para el manejo de las "cadenas" o arrays
de caracteres.

Razones de espacio y el nimo de no oprimir en demasa al lector causan


que no pueda ser ms explcito en lo que ejemplos de ficheros y "strings" se
refiere. Existe, por otro lado, la creencia generalizada de que los streams
slo sirven para el manejo elemental de las operaciones de i/o, debiendo
acudir a una librera de clases especializada (incluida o no en una librera
para la creacin de GUI's) para encontrar desarrollos complejos de estas
operaciones. Bien, sta es parte de la verdad: en muchos de los casos
demuestra nicamente que las libreras por defecto son fcilmente
extensibles, mientras que las otras veces indica la gran flexibilidad de C++
para implementar desarrollos "propietarios" de interfaces "a medida".

Mi consejo: slo se aprende a programar en C++ programando y


estudiando buenos programas en C++, y puesto que la librera iostream es
una excelente muestra de cuanto puede dar de s el lenguaje, a la vez que
explicita las facilidades de ste para la OOP, slo puedo repetir: lean, lean,
estudien, lean, lean, amplien.

C++/OOP: UN ENFOQUE PRCTICO Pgina 9/297


CUANDO IRRUMPEN
7
LOS "CONSTRUCTORES"

En este captulo despachar el sustrato bsico que sostiene a constructores y


destructores, aunque teniendo siempre en mente las dificultades de
comprensin que este apartado, en un primer contacto, suele generar.
Cualquier programador de un lenguaje tradicional, y por ende de C, no
admite sorpresas en cuanto a lo que un determinado cdigo puede ofrecer:
no hay ms ni menos que lo exactamente escrito. Para infortunio de estas
personas, sin embargo, una insignificante porcin de cdigo en C++ puede
encerrar, bajo su inocente apariencia, una compleja y sutil maraa de
operaciones invisibles no reflejadas expresamente en el cdigo. Realmente
esto suele poner muy nerviosos a ciertos programadores, que siempre
tienen la sensacin de no controlar del todo lo que est ocurriendo "de
verdad" cuando se ejecuta su cdigo. Este nerviosismo se acrecienta an
ms, si cabe, cuando se utiliza un depurador y el desarrollador novicio
observa, anonadado, cmo en un determinado momento pasa a
encontrarse, sin llamada expresa, en el cuerpo de un constructor; cmo,
tambin, ciertas lneas nunca se ejecutan porque en el momento menos
esperado aparece inmerso en el cuerpo de un destructor; cmo, en otra
ocasin, se ejecuta una extraa funcin apuntada por un handler cuya
existencia desconoca. Con tales precauciones desenfundadas intentar,
pues, explicar cmo se construyen y destruyen objetos en la prctica, de tal
forma que supeditar el rigor formal al didactismo, aun a riesgo de resultar
descorazonadamente simple. Pongmonos sin ms dilacin el casco y
pasemos al interior.

INICIALIZACIN DE OBJETOS

En captulos anteriores revisamos brevemente conceptos tales como el


puntero implcito this, las funciones y datos miembros, el calificativo const,
etc. O sea, hemos empezado a escudriar la estructura interna de las clases,
siendo as que parece que por un lado tenemos la "filosofa de objetos" y por
otro la "sintaxis de clases". S, s, esto est muy bien, Pero, qu pasa con la
transicin prctica desde la clase a los objetos? O mejor: dado que los

C++/OOP: UN ENFOQUE PRCTICO Pgina 10/297


objetos son instancias de clases, cmo se instancian stas para producirlos?
Cmo, en definitiva, se "construyen" objetos? Veamos el problema con un
ejemplo:

class Direccion {
private:
char* calle;
long cp;
char* ciudad;
// ...
};

class Persona {
private:
char* nombre;
Direccion* direccion;
// ...
};

Tenemos que la clase Persona contiene un puntero a un objeto de la clase Direccion. Examinemos el
funcionamiento de una variable de tipo predefinido: si en un bloque local codificamos

float objetoDeTipoPredefinido;

el compilador automticamente inicializar el objeto, reservando espacio


para su alocacin en memoria, a la vez que proceder a su destruccin al
salir del mbito local en que ha sido declarado. Qu ocurre, sin embargo,
con los objetos de tipo definido-por-el-usuario mediante clases? cmo se
inicializan estos? La respuesta es clara: inicializando la totalidad de sus
miembros. Y esto, cmo se lleva a cabo? Bueno, una solucin inmediata
podra ser dotar de funciones de inicializacin para cada miembro que seran
llamadas secuencialmente, como por ejemplo:

class Direccion {
public:
void estableceCalle( const char* miCalle ) {
calle = new char[ strlen( miCalle) + 1 ];
strcpy( calle, miCalle );
}
void estableceCp( const long miCp) {
cp = miCp;
}
void estableceCiudad( const char* miCiudad ) {
ciudad = new char[ strlen( miCiudad ) + 1 ];
strcpy( ciudad, miCiudad );
}
// ...
};

class Persona {
public:
void estableceNombre( const char* miNombre ) {

C++/OOP: UN ENFOQUE PRCTICO Pgina 11/297


nombre = new char[ strlen( miNombre) + 1 ];
strcpy( nombre, miNombre );
}
void estableceDireccion( Direccion* miDireccion ) {
direccion = miDireccion;
}
// ...
};

de tal forma que la inicializacin de un objeto de tipo Persona debera ser


realizada mediante, por ejemplo, la siguiente secuencia compleja:

Direccion miDireccion;
miDireccion.estableceCalle( "Orense, 36" );
miDireccion.estableceCp( 28020 );
miDireccion.estableceCiudad( "Madrid" );
Persona miPersona;
miPersona.estableceNombre( "John Doe" );
miPersona.estableceDireccion( &miDireccion );

Pero no, esto es ridculo!. Imaginemos una secuencia compleja de objetos


contenidos en otros objetos que a su vez contienen a otros: para inicializar
un simple objeto deberamos conocer cmo se inicializan multitud de otros
objetos, y esto atenta contra el ncleo de la OOP: cada objeto debe
encapsular sus propios mtodos de inicializacin. O sea, quin ha de saber
ms de inicializar un objeto que el objeto en s? Una solucin ms depurada
podra ser la inclusin en cada objeto de una funcin especial de
inicializacin, que podramos denominar init:

class Direccion {
public:
void init( const char* miCalle,
const long miCp,
const char* miCiudad)
{
calle = new char[ strlen( miCalle) + 1 ];
strcpy( calle, miCalle );
cp = miCp;
ciudad = new char[ strlen( miCiudad ) + 1 ];
strcpy( ciudad, miCiudad );
}
// ...
};

class Persona {
void init( const char* miNombre = "",
const char* miCalle = "",
const long miCp = 0,
const char* miCiudad = "" )
{
nombre = new char[ strlen( miNombre ) + 1 ];
strcpy( nombre, miNombre );
Direccion miDireccion;

C++/OOP: UN ENFOQUE PRCTICO Pgina 12/297


miDireccion.init( miCalle, miCp, miCiudad );
direccion = &miDireccion;
}
// ...
};

De esta forma la inicializacin de un objeto de tipo Persona se codificara de


la siguiente guisa:

void evaluaBushGate()
{
Persona candidatoDemocrata;
// en la siguiente lnea el resto de argumentos
// se aplica por defecto
candidatoDemocrata.init( "Bill Clinton" );
// realiza algn tipo de proceso
}

Lo cierto es que las cosas se han simplificado bastante. Pero en este


esquema hay algo que no funciona: Qu ocurre con el objeto candidatoDemocrata
cuando la funcin en cuyo mbito local se ha declarado termina? Debemos implementar una nueva funcin,
quizs denominada fin, que deba ser expresamente llamada antes del fin del mbito en que se declara un objeto
local? Qu tipo de desastre puede ocurrir si usamos el objeto definido antes de inicializarlo? No estamos confun-
diendo, por otra parte, la inicializacin de un objeto con la asignacin de valores a sus datos miembros? Veamos
qu ocurre con los objetos de tipo predefinido:

void hazNoSeQue()
{
char[ 16 ] nombrePresidente; // inicializacin
nombrePresidente = "Felipe Gonzlez" // asignacin
// ...
} // fin del mbito local: desinicializacin
// o destruccin de las variables locales

Realmente no tenemos que preocuparnos por inicializar expresamente las


variables de tipo predefinido, como tampoco tenemos que expresamente
desinicializarlas al final del mbito en que se han definido. Existe, pues, una
clara diferencia con respecto a nuestro anterior intento de construccin de
objetos. O sea, de alguna manera el compilador se ocupa de llamar a un
"constructor" para la inicializacin de objetos (variables) de tipo predefinido,
usando, en su caso, una suerte de "destructor" para liberar el espacio
ocupado por ste al salir de un determinado mbito. En la realidad no ocurre
exactamente as, pero esto nos da una idea aproximada de un enfoque que
podemos aplicar a los objetos instanciaciones de nuestras clases.

C++/OOP: UN ENFOQUE PRCTICO Pgina 13/297


CONSTRUCTORES

Vemos, pues, que en el caso de variables globales o estticas (siempre de


tipo predefinido) el "constructor" predefinido del compilador las inicializa a
cero, mientras que las variables locales se "construyen" sin valor fijo. Qu
ocurre, empero, con "nuestros" objetos? Exactamente lo mismo! El
compilador, de hecho, provee automticamente un constructor por defecto
para cada clase. Pero, qu demonios realiza este constructor y cundo entra
en accin? Bien: el constructor de un determinado objeto es llamado cuando
tal objeto es definido, igual que ocurre con los objetos de tipo predefinido, y
su funcionamiento es el siguiente: secuencial y recursivamente se inicializan
o "construyen" los distintos datos miembros de los objetos. O sea, si
tenemos

class Evento {
private:
long claveEstadistica;
Racional probabilidad;
// ...
};

class Racional {
private:
int numerador, denominador;
// ...
};

la siguiente codificacin

Evento tiradaDeDado;

causar que se aplique el siguiente esquema de "constructores" por defecto:


primero se "construir" por el compilador, de la forma usual, un objeto de
tipo long, para seguidamente construir un objeto de tipo Racional, para lo
cual, recursiva e iterativamente, se aplicarn los constructores por defecto
para cada uno de los datos miembros de esta ltima clase: o sea, se
aplicarn dos "constructores" predefinidos para los dos objetos de tipo int
correspondientes a los identificadores numerador y denominador. Naturalmente, y de forma
parecida a como operan los constructores, el compilador proporciona tambin unos "destructores" por defecto
que oportunamente liberarn los objetos construidos. Vemos, de esta manera, que el tratamiento de construc-
cin de objetos de cualquier tipo es homogneo. De todas formas este planteamiento de constructores implcitos
no nos soluciona de forma clara el problema, pues es posible que nosotros deseemos inicializar un objeto de
forma distinta a lo establecido por defecto. Y la verdad es que si no pudiramos construir un objeto a nuestro
gusto -dentro de ciertos lmites razonables-, la flexibilidad de que C++ tanto hace gala en el diseo de nuevos
tipos de datos abstractos quedara peligrosamente emborronada. Bien, lo cierto es que s podemos: y es aqu
donde con propiedad entran en liza los constructores, que, en definitiva, son funciones miembros de una
determinada clase, con una particular sintaxis, que proporcionan los mtodos para construir o instanciar objetos
de tal clase. Los constructores poseen como identificador el mismo de la clase a que pertenecen, y no poseen
valor ni tipo alguno de retorno. Veamoslo con un ejemplo:

class ControlMilitar {

C++/OOP: UN ENFOQUE PRCTICO Pgina 14/297


private:
char sexo;
int edad;
public:
ControlMilitar(); // no devuelve valor alguno,
// ni siquiera 'void'
};

ControlMilitar::ControlMilitar()
{
sexo = 'V';
edad= 18;
}

de tal forma que la lnea de cdigo siguiente,

ControlMilitar ejercicioActual;

donde se define un objeto de tipo ControlMilitar, origina que se produzca una llamada al
constructor implementado por nosotros, de manera que los datos miembros del objeto ejercicioActual se inicializan
a 'V' y '18'. Hemos sustituido, de hecho, al constructor "implcito" del compilador para esta clase. Pero la situacin
sigue sin ser totalmente satisfactoria: y si deseramos parametrizar la inicializacin de nuestro objeto? por qu
limitarnos a un rango de valores por defecto? Bien, es razonable suponer que, dado que los constructores son en
realidad funciones miembros, admitirn argumentos en tipo y nmero arbitrarios. Recodifiquemos, a efectos de
ejemplo prctico, la clase anterior:

class ControlMilitar {
private:
char sexo;
int edad;
public:
ControlMilitar( char miSexo, int miEdad )
{
sexo = miSexo;
edad = miEdad;
}
};

En esta ocasin un objeto de nuestra clase nicamente podra construirse


con la siguiente notacin:

ControlMilitar ejercicioActual( 'V', 17 );

otorgndonos ms control sobre el estado interno del objeto. Qu ocurre,


sin embargo, si con la anterior definicin de la clase codificamos la siguiente
inofensiva lnea?

ControlMilitar ejercicioActual;

El lector podra presumir que, dado que hemos proporcionado un


constructor con un nmero y tipo de argumentos que no encaja con esta
definicin del objeto, entrar en accin el constructor implcito del

C++/OOP: UN ENFOQUE PRCTICO Pgina 15/297


compilador comentado lneas atrs. El lector, en efecto, podra sostener tal
presuncin, pero se equivocara: el hecho que hayamos definido uno o ms
constructores (pues, como el lector ya habr inferido, merced a la
sobrecarga de funciones y dado que el constructor es bsicamente una
funcin, se pueden definir distintos constructores para una misma clase)
anula tal constructor implcito, por lo que el uso del objeto tras la lnea
anterior podra conducir a un resultado impredecible, pues ste no ha sido
propiamente inicializado. Quiere esto decir que debemos expresamente
implementar en nuestra clase un constructor sin argumentos? Bien, en
principio podramos pensar que nadie nos obliga por la fuerza a conducirnos
as: slo habra que tener la precaucin de codificar siempre la inicializacin
de los objetos con el nmero y tipo de argumentos significados en nuestros
constructores. Veamos que, de nuevo, sin embargo, esto no funciona:
imaginemos que deseamos inicializar un array de objetos del tipo

ControlMilitar arrayDeControlesMilitares[ 10 ];

Qu constructor usar el compilador para inicializar los objetos del array?


El constructor sin argumentos! Pero no hemos definido tal constructor y el
implcito por defecto ha sido "cancelado" al implementar otro constructor,
por lo que una posible, tediosa y a veces posiblemente impracticable
solucin sera explicitar la inicializacin de cada objeto con el constructor
disponible, como por ejemplo:

ControlMilitar arrayCM[ 2 ] = { ControlMilitar( 'V', 17 ),


ControlMilitar( 'V', 18 ) };

En realidad estamos "parcheando" una evidente carencia, cuando lo ms fcil


es suplirla: dotemos, pues, a la clase con tal constructor por defecto:

class ControlMilitar {
public:
ControlMilitar() {};
// ...
};

Y ya est? -preguntar el asombrado lector-, esto es todo? una funcin


sin argumentos y con un cuerpo vaco, que no hace absolutamente nada?
Dnde queda aqu la inicializacin de los datos miembros de nuestra clase?
Tranquilidad, amable lector. Resulta que el lenguaje C++ establece que si
un constructor no inicializa de forma expresa a los miembros de su clase
(tanto como a sus clases base, si stas existen, como veremos ms
adelante), automticamente entrar en accin el constructor por defecto para
cada uno de ellos. En el caso que nos ocupa, como quiera que el constructor
ControlMilitar() no inicializa expresamente ningn miembro, el resultado es que al ejecutarse produce que primero
se aplique el "constructor" de tipo char a la variable sexo, y despus el "constructor" de tipo int a la variable
edad, que quedarn, como variables locales, en un estado indefinido. O sea, el mismo comportamiento que si no
hubiramos definido ningn constructor en la clase y se aplicara el constructor implcito por defecto. De hecho la
anterior codificacin viene a decir: "queremos uno o ms constructores concretos para nuestros objetos, pero sin
renunciar a la aplicacin del constructor por defecto", y ello conseguido con un mnimo esfuerzo escritor.

C++/OOP: UN ENFOQUE PRCTICO Pgina 16/297


Bueno: parece que el panorama se va aclarando. Examinemos, de cualquier
forma, una postrera posibilidad: un constructor con argumentos por defecto.
Vemoslo:

class ControlMilitar {
public:
ControlMilitar( char = 'V', int = 18 );
private:
char sexo;
int edad;
// ...
};

ControlMilitar::ControlMilitar( char miSexo, int miEdad )


{
sexo = miSexo;
edad = miEdad;
}

Quedaran as grandemente resueltas nuestras preocupaciones. Pero


miremos con ms detenimiento en el cuerpo de nuestro constructor. Qu
vemos? Pues la simple asignacin de dos objetos de tipo char e int. Nada de
construccin de objetos. Recordemos la regla expuesta un poco ms atrs: si
en un constructor un miembro no se inicializa de forma expresa, cual es ste
el caso, entrar en accin el constructor por defecto para tal miembro. O
sea, que la aplicacin de este ltimo constructor originar que, antes de
ejecutarse el cuerpo del mismo, se construyan los objetos de tipo int y char,
para despus proceder a la asignacin a ambos de determinados valores.
Estamos, de hecho, duplicando innecesariamente el trabajo, pues lo ideal
sera que el constructor asignara directamente los valores deseados. En
realidad existe una sintaxis especfica a este fin:

ControlMilitar::ControlMilitar( char miSexo, int miEdad )


: sexo( miSexo ), edad( miEdad) { }

Los dos puntos tras el constructor indican que seguir, antes de que
comience el cuerpo del mismo, una lista de inicializadores de los datos
miembros de la clase (y tambin de las clases base, en su caso). Vemos,
tambin, que ste constructor posee un cuerpo vaco. Naturalmente! Este
cdigo indica expresamente cmo deben ser construidos los objetos
miembros, a la vez que, en la misma operacin, les asigna los pertinentes
valores, an antes de comenzar la ejecucin del cuerpo de la funcin
constructora que, en este caso, no aportara nada nuevo. En lo posible
debemos usar la sintaxis de inicializacin de miembros en lugar de la de
asignacin en constructores, aunque en algunas ocasiones esto ser
obligatorio: los datos miembros referencias o const no se pueden asignar,
sino slo inicializar.

C++/OOP: UN ENFOQUE PRCTICO Pgina 17/297


Repasemos esta curiosa sintaxis de inicializacin en constructores. Se ha
fijado el lector si los datos miembros aparecen en la lista de inicializacin en
el mismo orden en que aparecen declarados en el protocolo de descripcin
de su clase? Demonios! Qu es esto? -inquirir con cierta razn el lector-
Una nueva sutileza? Bien, algo parecido: qu orden, por ejemplo, sigue un
constructor por defecto en la "construccin" de los datos miembros de su
clase? Con cierta lgica sigue el orden de aparicin de la declaracin de los
miembros en la clase dada. Qu ocurre, sin embargo, si cambiamos este
orden en nuestra lista de inicializacin? Podemos as elegir el orden de
construccin de nuestros datos miembros? Bien, la verdad es que no. El
orden de "construccin" de los miembros permanecer, en cualquier caso,
invariante. Vaya! Y, por qu? Pues, bsicamente, porque los destructores
(algo as como el reverso de los constructores, y que veremos enseguida)
siguen un orden opuesto al de los constructores asociados, de tal forma que
el sistema debe controlar este ltimo orden, de forma que si tal dependiera
de nuestro capricho el sistema sufrira una intil penalizacin. Tiene algn
sentido, entonces, situar los inicializadores de la lista en el orden correcto?
Por supuesto! Es una forma de evitar errneas interpretaciones en la
evaluacin del orden de los constructores, reforzando, de paso, la idea que
el programador sabe exactamente qu es lo que est haciendo.

Revisemos ahora otras formas usuales de construccin de objetos de tipo


predefinido:

char tuLetra = ''; // constructor


char miLetra = tuLetra; // constructor
miLetra = tuLetra; // constructor?

Las dos primeras lneas construyen dos objetos de tipo char y, en el mismo
acto, copian el valor situado a la derecha del signo '=' a cada uno de ellos.
En la primera lnea, en puridad, se construye primero un objeto temporal de
tipo char a partir del carcter '', y seguidamente se copia tal objeto en el
objeto de nueva construccin con identificador tuLetra. En la segunda lnea
se produce, sin ms, una construccin con copia simultnea. A este tipo de
constructores se les denomina, vctimas de una imaginacin lxica
sorprendente, constructores de copia. Examinemos ahora la ltima lnea de
cdigo: se sustituye la representacin interna del objeto a la izquierda del
operador '=' con la del objeto a la derecha de ste. En definitiva, un objeto
se asigna a otro pero, como el atento lector ya habr notado, no se
construye ningn objeto, sino que se trata de una operacin entre objetos
"ya construidos"; una operacin correspondiente al operador de asignacin
(operator=). De hecho, para distinguir entre asignacin e inicializacin
debemos preguntarnos: se ha construido algn objeto? En caso negativo
podramos inferir que se trata de una asignacin.

Al igual que hemos visto anteriormente, si no proporcionamos de forma


expresa constructor de copia y operador de asignacin a una determinada
clase, el compilador suplir tales funciones implcitamente con el siguiente
comportamiento: en el caso del constructor de copia, se construir un objeto

C++/OOP: UN ENFOQUE PRCTICO Pgina 18/297


y seguidamente se inicializarn sus datos miembros con los mismos valores
del objeto a copiar; en el caso del operador de asignacin simplemente se
copiarn los valores de los datos miembros de un objeto a otro. Bueno, esto
parece suficiente -podra afirmar, ya cansado, el lector- Existe alguna razn
para codificar nuestras propias versiones de tales funciones? S, a veces.
chenle un ojo, si no, al siguiente ejemplo:

class Persona {
public:
Persona( char* miNombre = 0, int miEdad = 0 )
: edad( miEdad )
{
if ( miNombre ) {
nombre = new char[ strlen( miNombre ) + 1 ];
strcpy( nombre, miNombre );
} else {
nombre = new char[ 1];
*nombre = '\0';
}
}
private:
int edad;
char* nombre;
// ...
};

Persona primeraPersona( "Luis", 31 ),


terceraPersona( "Antonio", 27 );
// constructor de copia
Persona segundaPersona = primeraPersona;
// operador de asignacin
terceraPersona = primeraPersona;

De acuerdo con lo dicho, los datos miembros de los objetos segundaPersona y


terceraPersona son los mismos que los del objeto primeraPersona. Esto quiere decir, por ejemplo, que el miembro
edad tiene el valor de '31' en todos los objetos. Qu ocurre, empero, con el miembro nombre? Pues que en los
objetos se ha copiado el valor del puntero que apunta a la cadena de caracteres "Luis" del objeto
primeraPersona. O sea, los tres miembros de los objetos apuntan a la misma cadena. Pero es muy posible que
esto no sea lo que desebamos conseguir. Imaginemos que si, por ejemplo, destruimos uno de los objetos y se
"destruye" la cadena, los restantes objetos quedarn en una situacin cuando menos curiosa, si no desastrosa.
Cmo podramos solucionar esta dificultad? Pues codificando nuestras propias funciones:

class Persona {
public:
Persona( const Persona& miPersona )
{
edad = miPersona.edad;
nombre = new char[ strlen( miPersona.nombre ) + 1 ];
strcpy( nombre, miPersona.nombre );
}
Pesona& operator=( const Persona& miPersona )
{
if ( this == &miPersona )//chequea autoasignacin

C++/OOP: UN ENFOQUE PRCTICO Pgina 19/297


return *this;
edad = miPersona.edad;
delete [] nombre; // desaloja espacio almacenamiento
// libre antigua cadena
nombre = new char[ strlen( miPersona.nombre ) + 1 ];
strcpy( nombre, miPersona.nombre );
return *this;
}
// sigue resto de la descripcin de la clase
};

Vemos tambin, de paso, un primer ejemplo sencillo de sobrecarga de


operadores en el mbito de una clase, que ampliaremos en un captulo
posterior. Bien, en definitiva, con esta codificacin conseguimos que se
copien las cadenas de un objeto a otro, y no slo los punteros.

Consideremos, para terminar este pargrafo, la construccin de un objeto


apuntado por un puntero:

Persona* punteroAPersona;
punteroAPersona = new Persona( "Roque", 52 );

DESTRUCTORES

As como existen funciones constructoras para nuestros objetos, de la misma


forma existen sus opuestas: las encargadas de "desinicializar" lo inicializado:
los destructores. La nica diferencia conceptual es que mientras que puden
ser definidos varios constructores, slo es posible definir un destructor por
clase, lo cual es lgico, pues existen, por ejemplo, infinidad de figuras a ser
construidas mediante la papiroflexia, pero slo una forma de quemarlas.

Si no dotamos de destructor expreso a una clase, el sistema, al igual que


ocurra con los constructores, asumir uno por defecto, y cuyo
funcionamiento consistir exactamente en las operaciones inversas de las
que realizara el constructor por defecto. Sin embargo este planteamiento no
servira, verbigracia, para nuestro ltimo ejemplo, pues en los constructores
alojamos una cadena en el espacio de memoria de almacenamiento libre,
mediante el operador new, y si simplemente dejamos que acte el destructor
por defecto, nicamente se "destruir" el puntero, pero no ser liberada la
memoria utilizada para la cadena. Qu debemos, pues, hacer? Codificar
nuestro propio destructor, naturalmente. Los destructores no poseen tipo de
retorno ni argumentos y ostentan como identificador el mismo que el de la
clase antecedido por la tilde ~. Vemoslo:

class Persona {
public:
~Persona()
{
delete [] nombre;

C++/OOP: UN ENFOQUE PRCTICO Pgina 20/297


}
// sigue resto descripcin de la clase
};

Al igual, tambin, que ocurra con los constructores, si en un destructor no


se desinicializa expresamente un dato miembro, automticamente actuar el
destructor por defecto para el mismo.

Pero, entonces, cundo debe ser usada una funcin "destructora"? Bien, lo
cierto es que raras veces es necesario llamar expresamente a un destructor.
La inmensa mayora de las veces ste es usado de forma implcita por el
compilador, producindose una llamada automtica al mismo en cualquier
momento desde la ltima aparicin de un objeto y el fin del mbito del
mismo.

UN SENCILLO CONTROL

Este es un buen momento para explicitar una pequea triquiuela, muy


habitual, por otra parte, en los textos prcticos de introduccin al lenguaje:
en tanto el lector no conozca con precisin la mecnica del lenguaje con
respecto a estas particulares funciones miembros, es francamente
recomendable incluir lneas del tipo imprime "Construccin del objeto X
mediante el constructor de copia", o imprime "Destruccin del objeto Y". Al
ejecutarse el cdigo el lector podr perfectamente apreciar en qu momento
se activan los constructores y destructores.

C++/OOP: UN ENFOQUE PRCTICO Pgina 21/297


DE LA OBJETIVIDAD
8
DE LO RACIONAL

En fin, hasta ahora hemos visto bastante de teora y muy poco de cdigo
prctico. Y es hora de que pongamos las manos en la masa. Por eso ste
prembulo ser excepcionalmente corto. Tan corto que ya se acab.

DEFINAMOS UN NUEVO TIPO: LA CLASE "RACIONAL"

En los captulos precedentes han aparecido frecuentes referencias,


acompaadas por cortos fragmentos de cdigo, a una clase denominada
Racional que, en sntesis, pretenda encapsular las caractersticas del
conjunto matemtico de los nmeros Racionales en una clase, constituyendo
lo que se denomina un ADT (Abstract Data Type: Tipo de Dato Abstracto).
Bien, ya hemos llegado a un punto del relato en que podemos abordar la
construccin de dicha clase, e intentaremos hacerlo con cierto mtodo, a la
vez que explicando cada decisin de diseo. Vayamos a ello.

Qu es, en definitiva, un nmero racional? Bien, la definicin matemtica


comprehensiva sera la siguiente (y obviemos la notacin especfica):

Racionales = { x/y | x es un nmero Entero e


y es un nmero Entero }

O sea, un racional es un nmero representado por dos nmeros enteros


(ojo: enteros matemticos) separados por el operador de divisin: lo que los
nios conocen como "quebrado". El nmero a la izquierda del operador '/' se
denomina numerador, y el otro denominador. En esencia, pues, se trata de
un par de nmeros relacionados por un operador. Bien, ya podemos intentar
un primer acercamiento a la compsicin interna de los objetos de tipo
Racional:

class Racional {
private:
int numerador;
int denominador;

C++/OOP: UN ENFOQUE PRCTICO Pgina 22/297


};

Hemos declarado los datos internos como privados, siguiendo los esquemas
de encapsulacin y ocultacin de la informacin preconizados por la OOP.
En general los datos miembros de una clase se declararn private, dejando
el interfaz de cliente (la parte public) nicamente para funciones miembros.
El cliente de la clase14 no tendr as dudas, entre otras cosas, sobre aadir o
no parntesis funcionales a los miembros del interfaz pblico (situacin
resuelta, por otro lado, en el lenguaje Eiffel por la no diferenciacin
sintctica entre datos y funciones miembros). Pensemos tambin que
podramos cambiar, por ejemplo, el tipo de estos datos (pasndolos a long),
o aadir un nuevo dato miembro: su acceso exclusivo a travs de funciones
miembros pblicas nos asegura que en caso de tales cambios (que sern
oportunamente reflejados en la implementacin de tales funciones) el cdigo
de los clientes de la clase no deber ser en absoluto variado.

Qu ocurre, empero, con el operador de divisin? Realmente no forma


parte de la representacin interna de los objetos Racionales? No, diantre!
nicamente forma parte de su representacin visual o grfica! Y no es, por
otro lado, el verdadero nmero racional el cociente resultante de dividir
numerador por denominador? Ya hemos visto que no, al definir el conjunto
de los Racionales. En realidad tal nmero decimal podra considerarse como
la conversin a float de un Racional. Como vemos, es conveniente aclarar las
ideas ante la siempre difcil tarea de definir una clase, descubriendo a veces
que lo obvio no es lo ms apropiado.

De acuerdo, pensar el lector: aceptemos la privacidad de los datos internos.


Inmediatamente, entonces, deberemos dotar a la clase de funciones pblicas
de acceso a tales datos, para permitir su inicializacin y modificacin.
Tranquilo, amable lector! Cada cosa en su momento! Lo primero que
debemos hacer, como ya creemos tener completa la representacin interna
de la clase, es dotar a sta de constructores y destructor apropiados. Veamos
primero los constructores.

Como vimos en el captulo anterior, si no dotamos de un constructor


expreso a nuestra clase, el sistema proveer un constructor implcito por
defecto (sin argumentos), lo que en este caso no parece suficiente. En
general podemos pensar que la construccin de un objeto Racional requerir
dos argumentos, justos los correspondientes a los datos miembros
numerador y denominador:
14
Hay que tener en cuenta que C++ enfatiza sobremanera la reutilizacin del
cdigo, fundamentalmente mediante la derivacin de clases. Debe pensarse, en
esta tesitura, que el cdigo de nuestras clases, al menos en lo que se refiere al
interfaz o archivos de cabecera, ser revisado por desarrolladores o usuarios, a
quienes genricamente denominaremos clientes y cuya vida nos guardaremos
mucho de complicar innecesaria mente. Son clientes, tambin, los objetos que
hacen uso del interfaz de nuestra clase.

C++/OOP: UN ENFOQUE PRCTICO Pgina 23/297


class Racional {
public:
Racional( int, int );
private:
int numerador;
int denominador;
};

Como el lector notar, estamos diseando con cruel lentitud la clase


ejemplo, pero existen tres razones bsicas para ello: primera: esto es una
introduccin gentil al lenguaje; segunda: el diseo eficiente de clases, como
ya ha quedado dicho, es ciertamente difcil; tercera: lo obvio en C no tiene
por qu serlo en C++ (y, de hecho, en lo que se relaciona con las clases, la
mayora de las veces no lo es).

Bien, ya hemos declarado un constructor. Implementamos ahora su


definicin? No! Vamos a despachar primero un intento de interfaz completo
de la clase (slo declaraciones de datos y funciones miembros, incluso de las
inline), pues esto allanar grandemente su implementacin posterior.
Intentaremos, pues, seguir esta norma, aunque sin fanatismos, pues en
C++ muchas veces hay que volver la vista atrs para desandar lo andado15,
as como adelantar para adivinar lo pasado. Sigamos, entonces, con los
constructores. Con arreglo a lo codificado podemos construir objetos
Racionales de la siguiente forma:

Racional dosQuintos( 2, 5 );
Racional ochoTercios( 8, 3 );
Racional nueve( 9, 1 ); // Todos los enteros son Racionales!
Racional cero( 0, 1 );

aunque, qu ocurre si codificamos lo siguiente?:

Racional fraccion; // error!:


// no existe el contructor Racional()

Pues que, al haber anulado el constructor implcito con nuestro constructor


de dos argumentos, el compilador flagelar como error tal lnea. La solucin
ms inmediata sera la declaracin expresa de un constructor por defecto:

class Racional {
public:
Racional();

15
En el desarrollo de sistemas software en C++ es frecuente la aplicacin de la
mxima "analizar un poco, disear un poco, codificar un poco", de tal forma
que el proceso de creacin se convierte en un iterativo refinamiento de cada etapa
en sucesivos ciclos, involucionndose stos de manera que se quiebra el tradicional
esquema de "cascada" de fases.

C++/OOP: UN ENFOQUE PRCTICO Pgina 24/297


Racional( int, int );
private:
int numerador;
int denominador;
};

Tal constructor por defecto posibilitara dos codificaciones. La primera, con


el cuerpo vaco, de la siguiente guisa:

Racional::Racional() { ; }

inicializar los datos miembros de la forma usual en que el compilador


inicializa variables locales de tipo int: dejndolas con un valor indefinido.
Quiz esto no sea lo ms apropiado. La segunda codificacin proporcionara
valores por defecto a nuestros datos miembros:

Racional::Racional() : numerador( 0 ), denominador( 1 ) {}

Vemos que la construccin de un racional sin argumentos equivaldra, as, a


la construccin del racional '0/1'. Se ha preferido, por otro lado, la lista de
inicializacin frente a la asignacin de los datos miembros en el cuerpo del
constructor, segn se explic en el anterior captulo, siendo sta una
preferencia que el lector deber tomar como norma.

Tenemos, pues, dos constructores: uno con dos argumentos y otro por
defecto. Pero examinemos el constructor por defecto: en realidad inicializa
los datos miembros de la misma forma que lo hara el constructor con
argumentos, lo nico que con unos valores predefinidos. Por qu no unir
estos dos constructores en uno solo con dos argumentos a los que se
asignaran parmetros por defecto? Nuestra clase quedara ahora as:

class Racional {
public:
Racional( int = 0, int = 1 );
~Racional() {};
private:
int numerador, denominador;
};

Hemos incorporado, tambin, el destructor con el cuerpo vaco, pues no hay


nada concreto que hacer para desinicializar los objetos sino dejar que el
"destructor" de los datos miembros de tipo int acte como lo hara con
variables locales "normales". Ahora podramos codificar lo siguiente:

Racional unQuinto( 1, 5 );
Racional miFraccion; // equivale a miFraccion( 0, 1 )
Racional nueve( 9 ); // equivale a nueve( 9, 1 )

Por supuesto que otra posible solucin hubiera sido declarar parmetros por
defecto en el constructor con dos argumentos y mantener, a la vez, el

C++/OOP: UN ENFOQUE PRCTICO Pgina 25/297


constructor sin argumentos. Pero, claro, esto no es una solucin: es un
grave error. As las cosas la siguiente lnea:

Racional miObjetoRacional; // error: ambigedad

ocasionara un error por ambigedad en la llamada al constructor: se llama


al constructor de dos argumentos con los dos parmetros asumidos por
defecto? o bien se llama al constructor sin argumentos? Dejemos, pues, en
este caso, un solo constructor.

El lector debe ser, no obstante, cuidadoso a este respecto, pues si bien en


ARM se establece que un constructor con todos sus argumentos establecidos
por defecto es un constructor por defecto, algunos compiladores actuales se
niegan a aceptar este hecho, por lo que, en tales casos, habra que codificar
un constructor expreso sin argumentos que sera utilizado, por ejemplo, en
la construccin de un array de objetos. No debemos permitir, de cualquier
manera, que las deficiencias de algunos compiladores enturbien nuestros
esquemas de codificacin16.

Qu hubiera ocurrido, por otro lado, si el constructor hubiera sido


declarado en la seccin private de la clase? Pues que los objetos no podran
acceder a l y, por tanto, no podran ser construidos, con lo que las
anteriores lneas habran procurado sendos errores en compilacin. Quiere
decir esto que los constructores siempre habrn de ser pblicos? No, ni
mucho menos! Lbreme Pessoa de tales afirmaciones categricas en C++!
Los constructores privados se dan, por ejemplo, en clases anidadas, en
estructuras carta-sobre o formas de Singleton, como ya veremos -repitan
ustedes- ms adelante.

"Otra cosa", continuar el lector: Tenemos cada vez que reescribir la


descripcin de la clase para aadir miembros? No hay forma de
aadrselos, como apndices, tras la definicin de la clase? No, ciertamente!

16
Quiz lo ms prctico, en este y otros casos de desajuste entre ARM y
nuestra implementacin concreta de C++, sea encerrar el cdigo
desafortunadamente generado entre condicionales del preprocesador, como por
ejemplo:

class Racional {
public:
#ifdef CPP_NOCOMP
Racional() {}
#endif /* CPP_NOCOMP */
// sigue resto clase
};

pudiendo as aislar, en lo posible, tales deficiencias. En lo que a parmetros


por defecto en constructores se refiere, Borland C++ 3.X y 4.0 no plantean
ningn problema.

C++/OOP: UN ENFOQUE PRCTICO Pgina 26/297


Pensemos que si una clase se pudiera modificar con posterioridad a su
definicin todo nuestro esquema de acceso se vendra abajo, pues bastara
para ello que el cliente "aadiera" una funcin con acceso al miembro de la
clase que deseara, sin encontrar restriccin alguna. Sigamos.

Bien, ya sabemos que el sistema proporciona a nuestra clase un constructor


de copia y un operador de asignacin implcitos. La pregunta es: son
suficientes para nuestros propsitos, en lo que respecta a la clase Racional, o
ms bien deberamos definir nuestras propias versiones? Dado que nuestra
clase no maneja memoria del almacenamiento libre, parece que lo que
proporciona el sistema es suficiente17. De esta manera podemos escribir:

Racional dosTercios( 2, 3 ), tuFraccion( 7 );


Racional miFraccion = dosTercios; // constructor de copia:
// miFraccion "vale" 2/3
tuFraccion = miFraccion; // operador de asignacin:
// tuFraccion "vale" 2/3

SOBRECARGA DE OPERADORES

De acuerdo: ya tenemos la representacin interna y distintas formas de


construir los objetos. Qu sigue ahora? Bien, lo siguiente es completar el
interfaz de cliente de la clase. O sea, dotar a la clase de mtodos pblicos
que permitan la aplicacin de sus instanciaciones (objetos) de una forma
similar a como se hara con objetos de tipo predefinido. Esta es, en general,
una labor delicada, pues exige prudencia, experiencia y una cierta
comprensin de las sutilezas del lenguaje. Lo ms fcil es que el novicio
"hinche" el interfaz con un nmero elevado de funciones miembros,
previendo -y aun duplicando- todos los posibles casos de uso que se le
ocurran. Esto suele ser un error, pues normalmente confunde al cliente de la
clase, que se siente como si hubiera entrado en un parque de atracciones
que constara nicamente de montaas rusas, eso s, diferenciadas por
colores y por algn oscuro matiz que se le escapa al observador. El interfaz
que nos ocupa debe ser, pues, breve y completo (algo as como una
extensin informtica de la clebre mxima de Gracin). Resolvamos una
primera lista descriptiva de mtodos necesarios:

- comparacin: igualdad, mayor, menor.


- suma, resta, multiplicacin y divisin entre objetos.
- operaciones de entrada y salida de objetos.

17
De hecho, lo contrario puede afirmarse como regla: "siempre que una clase
haga uso de la memoria de almacenamiento libre (normalmente mediante el
operador new), el desarrollador deber proveerla de sus propias versiones del
constructor de copia y de la sobrecarga del operador de asignacin".

C++/OOP: UN ENFOQUE PRCTICO Pgina 27/297


Declaremos ahora las pertinentes funciones miembros correspondientes a
tales mtodos. Algo como lo siguiente:

typedef int boolean;

class Racional {
public:
boolean esIgualA( const Racional& ) const;
boolean esMayorQue( const Racional& ) const;
Racional suma( const Racional& );
// etc., etc.
};

Pero no, esto no funciona. Pensemos que para, por ejemplo, comparar dos
objetos de tipo Racional deberamos codificar, con arreglo a lo expuesto, lo
siguiente:

Racional miFraccion, tuFraccion;


// ...
if ( miFraccion.esIgualA( tuFraccion ))
hazAlgunaCosa();

Y es que esta sintaxis no se parece a la que usaramos, por ejemplo, con


floats, que sera algo as como:

float miFloat, tuFloat;


// ...
if ( miFloat == tuFloat )
hazAlgunaCosa();

Cul es la solucin? El uso de la caracterstica de sobrecarga de


operadores, naturalmente! Probemos, para empezar, con el ya preludiado
operador de igualdad:

class Racional {
public:
boolean operator ==( const Racional& );
// sigue resto descripcin clase
};

Examinemos con ms detenimiento la sobrecarga declarada: se trata de una


funcin miembro (con la especial sintaxis de los operadores) que devuelve
un valor booleano y toma como argumento una referencia a un objeto
constante de tipo Racional. Un argumento? Pero el operador ha de
comparar dos objetos! Qu pasa aqu? Bien, el argumento de nuestra
funcin se corresponde con el objeto a la derecha del operador, mientras
que el objeto correspondiente a la instanciacin de la clase, que recibe el
mensaje del operador (el que llama a la funcin operador==), se
corresponde con el objeto a la izquierda del operador.

De acuerdo con lo expuesto, ahora ya podemos escribir:

C++/OOP: UN ENFOQUE PRCTICO Pgina 28/297


Racional miFraccion, tuFraccion;
// ...
if ( miFraccion == tuFraccion )
hazAlgunaCosa();

pero siempre teniendo en cuenta que el uso infijo del operador equivale a la
siguiente notacin funcional:

if ( miFraccion.operator==( tuFraccion ) )
hazAlgunaCosa();

Probemos ahora con una operacin algebrica, como por ejemplo la suma
de Racionales. Veamos la declaracin de la sobrecarga:

class Racional {
public:
Racional operator+( const Racional& );
// sigue resto descripcin clase
};

Vemos que, de nuevo, el argumento es una referencia a un objeto constante


de tipo Racional. Pero, por qu una referencia y no un puntero o un simple
objeto pasado por valor? Bsicamente por una cuestin de eficiencia: si se
pasara un objeto como argumento tendra lugar la siguiente secuencia: en la
llamada a la funcin se copiara el objeto al identificador del argumento
mediante el constructor de copia (en nuestro caso mediante el constructor de
copia implcito), y en algn momento desde la ltima lnea de uso de este
objeto local hasta el fin del mbito de la funcin se llamara al destructor de
este objeto "copiado". Si el argumento es un puntero o una referencia no se
producen estas llamadas a constructor y destructor18, por lo que el resultado
es ms eficiente. La ventaja de que no se modifiquen los objetos pasados
como argumentos, propia del paso de los mismos por valor, se puede
obtener ahora declarando el objeto como constante. La ltima eleccin entre
puntero y referencia se salda, como es costumbre en C++, a favor de la
referencia, que posee una sintaxis ms clara y, en el fondo, fsicamente, es
un puntero constante a un objeto ya existente. El hecho, por otra parte, que
el objeto cuya referencia se pasa como argumento se declare como const
ayuda al compilador, pues ste ya sabe que el objeto no va a ser
modificado, a optimizar la llamada.

Propongamos, sin ms dilacin, a efectos de examen, una posible


implementacin de esta sobrecarga:
18
Y esto sin contar con la posibilidad que el objeto contenga otros objetos
instanciaciones de clases y que, recursivamente, seran copiados mediante sus
respectivos constructores de copia. No contamos, as mismo, con la posible
construccin para su paso por valor de las correspondientes partes de las clases
base de la clase dada.

C++/OOP: UN ENFOQUE PRCTICO Pgina 29/297


Racional Racional::operator+( const Racional& argDcho)
{
numerador = numerador * argDcho.denominador
+ denominador * argDcho.numerador;
denominador = denominador * argDcho.denominador;
return *this; // devuelve el objeto que ha recibido
// el mensaje, con su representacin
// interna convenientemente modificada
}

Esta definicin supone que si codificamos lo siguiente:

Racional unMedio( 1, 2 ), dosTercios( 2, 3 );


Racional fraccionSuma = unMedio + dosTercios;

obtengamos un valor interno para fraccionSuma de '7/6', lo cual es correcto. El objeto dosTercios
sigue representando el valor '2/3'. Pero, qu pasa con el objeto unMedio? Pues que su valor interno ha pasado
a ser '7/6'! Repasemos la secuencia: en primer lugar la aplicacin del operador suma equivale a la siguiente
notacin funcional:

unMedio.operator+( dosTercios );

que, asimilada a la implementacin de la funcin detallada ms arriba,


resulta en que los datos miembros del objeto a travs del que se llama la
funcin (al que va dirigido el mensaje del operador: esto es, el objeto unMedio)
son modificados y trocados por los resultantes de la suma algebrica de los racionales matemticos
representados por los objetos. O sea, que los datos originales del objeto unMedio se pierden para siempre.
Seguidamente, la aplicacin del operador devuelve el mismo objeto unMedio ya modificado, que es usado por el
constructor de copia implcito para la construccin del objeto fraccionSuma. Bueno, la verdad es que esto dista
bastante de lo que queremos conseguir con la operacin suma. Intentemos, pues, una implementacin distinta:

Racional Racional::operator+( const Racional& argDcho )


{
Racional temp( numerador * argDcho.denominador +
denominador * argDcho.numerador,
denominador * argDcho.denominador );
return temp;
}

Esto es otra cosa: ahora se crea un objeto temporal de tipo Racional, distinto
de los sumandos, utilizando el constructor de la clase con los argumentos
del resultado de la suma. Si volviramos al cdigo de aplicacin
inmediatamente anterior veramos que el objeto fraccionSuma resulta en valer '7/6',
mientras que el objeto unMedio no ha cambiado su representacin interna. Pero, por qu devolver un objeto
Racional en lugar de una referencia a un objeto Racional? No parece esto ltimo, de acuerdo con lo expuesto
anteriormente, mucho ms eficiente? Bien, es una buena pregunta. Intentar responderla. Si cambiamos el tipo
de retorno a Racional&, el cdigo anterior podra quedar, salvo eso, invariante: al ejecutar la ltima lnea de la
funcin se devolvera una referencia al objeto temp. Pero este ltimo objeto es local al cuerpo de la funcin, por
lo que al devolver una referencia al mismo estamos preludiando el desastre: tras ejecutar la sentencia de
retorno, devolviendo en este hipottico caso una referencia, el objeto temp ser desinicializado por el destructor
de su clase, de forma que la referencia, antes siquiera que podamos utilizarla, "apuntar" a un objeto que ya no

C++/OOP: UN ENFOQUE PRCTICO Pgina 30/297


existe. Diantre! -podra exclamar aqu el lector-, pero el objeto local temp no se destruye, de cualquier manera,
al salir del mbito de la funcin de operador, independientemente de si tal funcin devuelve un objeto o una
referencia, o aun un puntero? Efectivamente! Pero nicamente cuando se devuelve un objeto lo que se
19
devuelve no es el objeto local, sino una copia del mismo (antes, evidentemente, de ser destruido), que se
enmarcar en el mbito al que pertenece la expresin de llamada a la funcin.

De acuerdo, de acuerdo -pensar el lector-, pero y si creamos el objeto temp


y lo alojamos en el rea de almacenamiento libre con un cdigo parecido al siguiente?

Racional& Racional::operator+( const Racional& argDcho )


{
Racional temp = new Racional(
numerador * argDcho.denominador +
denominador * argDcho.numerador,
denominador * argDcho.denominador );
return temp;
}

As solucionamos el problema anterior, pero de hecho salimos de un pozo


para caer en otro ms profundo. Ahora devolvemos una referencia a un
objeto creado mediante el operador new. Esto quiere decir que deber ser
expresamente destruido mediante el operador delete, pero normalmente
esto es poco menos que imposible. Y si no examinen el siguiente cdigo:

Racional cero, uno( 1 ), dos( 2, 1 );


Racional suma = cero + uno + dos;

Aqu uno se suma a cero, devolviendo una referencia a un objeto Racional temporal, al que se sumar el
objeto dos, devolviendo una referencia a un nuevo objeto tambin temporal, con el que se construir el objeto
suma. Se han creado, pues, dos objetos temporales mediante el operador new, pero lo cierto es que por ser
innominados no podemos acceder a ellos (a no ser que, con una tozudez cercana a la de Icaro, despachemos,
extendidas por todo el cdigo, declaraciones y asignaciones de identificadores que permitan nominar y tomar las
direcciones de estos objetos temporales) , por lo que, en consecuencia, no podremos aplicarles el operador
delete, lo que originar que, poco a poco, ineludiblemente, contruibuiremos a gastar la memoria disponible por la
aplicacin, hasta el punto de poder llegar al fatdico mensaje de falta de memoria. Vemos, pues, que la mejor
solucin es la originalmente propuesta de devolucin de un objeto en lugar de una referencia.

LOS PELIGROS DE LAS SOBRECARGAS

Quiz habra que hablar, ms bien, de los peligros de la inteligencia: los


desarrolladores, torturados por un darwiniano afan de explicitar su
brillantez, suelen proyectar en su cdigo su personal identidad y, si cabe, su
ingenio. Y si en programacin estructurada esto es discutible, en el diseo
de clases se convierte, la mayora de las veces, en inaceptable, dando lugar,

19
Como ya se ha sealado repetidas veces en el texto, el paso por valor o
copia de un objeto se realiza mediante la aplicacin del constructor de copia , bien
implcito bien expreso, con el que cuentan todas las clases, como tambin ha
quedado suficientemente explicado.

C++/OOP: UN ENFOQUE PRCTICO Pgina 31/297


bsicamente, a una deformacin que podemos nominar como sobrecarga
no-intuitiva. Vemoslo con ms detalle, para lo que montar un pequeo
tour sobre el tema.

Como ya sabemos, no podemos "inventar" nuevos operadores: nicamente


podemos sobrecargar los existentes. Todos los existentes? No! Los
siguientes operadores no pueden ser sobrecargados:

:: . .*

Por qu? Pues porque ya tienen un significado preciso y, tras conocer los
mecanismos del lenguaje, intuitivo para el lector: tales operadores, entre
todos los dems, se aplican ya, con carcter predefinido, sobre clases y sus
instanciaciones. No se pueden sobrecargar, as mismo, los siguientes
operadores:

?: # ##

pues ya poseen, tambin, un significado muy preciso: una redefinicin de


los mismos nicamente podra causar confusin y tristeza en el lector.
Vemos, pues, que el lenguaje C++ tiende a evitar, aun en detrimento de la
flexibilidad, las situaciones susceptibles de generar una arbitraria apariencia
no-intuitiva del cdigo.

En sntesis, se pueden sobrecargar los siguientes operadores:

+ - * / % ^ & | ~ ->* =
< > += -= *= /= %= ^= &= |= <<
>> >>== <<== == != <= >= && || , ->
! ++ -- () [] new delete

y, de stos, los siguientes como unarios:

+ - * &

Las sobrecargas, por otro lado, respetarn la signatura (binaria o unaria) de


los operadores y mantendrn invariante el orden de precedencia de su
evaluacin, as como la direccin de su asociatividad. O sea, se respetar la
tabla que se detalla a continuacin, donde el lector agradecer la inclusin
de los operadores propios de C++ en la conocida relacin de operadores de
C, y en donde la direccin de la asociatividad est reflejada en la columna
encabezada por 'A', de forma que 'D' significa "asociatividad por la derecha",
mientras que 'I' equivale a "asociatividad por la izquierda". El orden de
precedencia de los operadores viene dado por el orden del bloque en la tabla
a que el operador se adscriba. As el primer bloque, formado por el
operador ::, es el de ms alta precedencia, mientras que el operador coma
es el de ms baja precedencia. No existen precedencias de evaluacin,
empero, respecto de los operadores adscritos a un mismo bloque.

C++/OOP: UN ENFOQUE PRCTICO Pgina 32/297


Los manuales de estilo de C++ enfatizan, tambin, la incoveniencia de
sobrecargar los siguientes operadores:

, || &&

Operadores Descripcin A
:: cualificador de acceso a mbito global D
:: cualificador de acceso a mbito clase I

->, . selectores de miembros de clases I


() llamada a funcin o constructor I
[] ndice de arrays I

~ complemento a cero de bits D


++, -- autoincremento, autodecremento D
sizeof tamao en bytes D
! negacin lgica D
+, - suma y resta unarias D
* desreferenciacin D
() conversin de tipo (cast: moldeo) D
new, delete alocacin de memoria de almac. libre D
& direccin-de D
->*, .* selectores de punteros miembros clases I
*, /, % relaciones multiplicativas I
+, - operadores aritmticos binarios I
<<, >> desplazamiento de bits I

<, <=, >, >= operadores relacionales I

==, != igualdad, inegualdad I

& operador AND sobre bits I

^ operador XOR sobre bits I

| operador OR sobre bits I

&& operador AND lgico I

|| operador OR lgico I

?: operador condicional aritmtico I

=, *=, /=, %=, +=, -=, <<=, operadores de asignacin D


>>=, &=, |=, ^=

, operador coma I

pues, en caso contrario, se perdera la cualidad de secuenciacin inherente a


los mismos.

C++/OOP: UN ENFOQUE PRCTICO Pgina 33/297


Como podemos apreciar, permanentemente se refuerza la idea de
sobrecargas naturales, que no modifiquen la operatividad intuitivamente
esperada de los operadores. Pero, cuidado, a veces lo que creemos ms
intuitivo nos puede llevar a extraas situaciones. Pensemos en nuestra clase
Racional: por qu no dotarla con un operador de exponenciacin? Y, para
este fin, qu mejor eleccin que la del operador '^', al que ya estamos
acostumbrados por otros lenguajes? Instalemos, a modo de prueba, esta
opcin:

class Racional {
public:
Racional operator^( const Racional& );
Racional operator+( const Racional& );
// sigue descripcin de clase
};

Ahora podramos codificar lneas como las siguientes:

Racional resultado, uno( 1 ), dos( 2 ), tres( 3 );


resultado = uno ^ dos + tres;

Cul es el valor interno del objeto resultado? Si aplicamos las reglas de precedencia de
operaciones que hemos aprendido en el grado escolar elemental, donde la exponenciacin precede a la suma,
tendremos que resultado contiene la fraccin '4/1'. Correcto? No! Estamos en C++, y la precedencia (y la
asociatividad, en su caso) viene dada por la tabla anterior! Y en tal tabla podemos apreciar que la precedencia
del operador '^' est varios niveles por debajo del la del operador '+'. Qu ocurre, entonces, con nuestra
expresin? Pues que resultado contendr la fraccin '1/1'! O sea, el uso del operador ms intuitivo para una
operacin nos ha llevado a un antinatural orden de evaluacin de la misma. Conclusin? Debemos evitar esta
sobrecarga. No veo por qu! -podra afirmar un lector-: slo bastara con explicitar la precedencia deseada de la
siguiente forma:

resultado = ( uno ^dos ) + tres;

De acuerdo: esto soluciona el problema pero, a la vez, obliga al cliente de la


clase al permanente uso de parntesis en su cdigo, lo que inadvertidamente
podra olvidarse o calificarse como superfluo conduciendo a errores del tipo
del expuesto. Tanto es as que la comunidad C++ expresamente
desaconseja el uso de este operador para significar la exponenciacin. Cul
operador, entonces? Bueno, esto todava est discutindose: recientemente
he recibido del comit ANSI X3J16 C++ el texto de una propuesta
relacionada con la adopcin por el estndar del lenguaje de un operador
autnomo de exponenciacin20. O sea: no somos nadie. Intentando ser

20
Este documento de Matthew H. Austern, titulado "Una propuesta para
aadir un operador de exponenciacin al lenguaje C++", X3J16/92-0099,
WG21/N0176, propone la adopcin por el lenguaje de dos nuevos operadores
estndar: *^ y *^=, donde el primero sera un operador binario de exponencia -
cin y el segundo uno del conocido tipo operador=, con un nivel especfico de
precedencia y pudiendo ser normalmente sobrecargados.

C++/OOP: UN ENFOQUE PRCTICO Pgina 34/297


prudentes, nuestra actuacin deber ser eclctica, ponderando el uso de
funciones expresas de la forma elevadoA(...) o pow(...).

Qu pasa, por otra parte, con los operadores unarios? Pues simplemente
que la funcin operadora miembro correspondiente no dispondr de
argumentos (recordemos que el objeto que llama a tal funcin sera su nico
operando). El problema, por llamarlo de alguna forma, podra aparecer con
los operadores de autoincremento y autodecremento (++, --) porque, en su
mbito de actuacin predefinido, operarn de forma distinta segn prefijen o
postfijen a su operando, y debemos encontrar alguna manera de expresar
esta caracterstica al sobrecargarlos. Realmente hasta AT&T C++ 2.1 no
haba posibilidad de diferenciar el uso prefijo o postijo de estos operadores:
siempre operaban como prefijos. AT&T C++ 3.0 ha cambiado el panorama,
aun a costa de un truco algo artificioso: el operador postfijo (para
distinguirlo del prefijo) incorpora un argumento adicional de tipo int,
ocupndose el compilador de proporcionarle un valor por defecto (que no
nos interesa por su inutilidad prctica):

class Racional {
public:
Racional& operator++(); // PREFIJO
Racional operator++( int ); // POSTFIJO
// sigue descripcin de clase
};

Pero, entonces, esto es una mera facilidad o se corresponde a la intencin


de que las sobrecargas prefijas y postfijas adecen su comportamiento a la
actuacin predefinida de las mismas? Es decir, la sobrecarga del operador
postfijo ++ en nuestra clase Racional deber devolver exactamente el objeto
Racional a que se aplique y despus incrementarlo en una unidad? Bien, esto
es lo ms intuitivo, y parece que tambin lo ms correcto. Cmo se
codificara? Vemoslo:

Racional operator++( int )


{
// usaremos el constructor de copia implcito
Racional temp = *this;
// ahora incrementamos en una unidad nuestro objeto
numerador += denominador;
// seguidamente devolvemos una copia de nuestro objeto
// antes de ser incrementado en la unidad
return temp;
}

Como el lector podr fcilmente comprender, teniendo en cuenta lo


expuesto en pargrafos anteriores, los operadores prefijos tendern a
devolver referencias a objetos (el mismo objeto convenientemente
modificado), mientras que los operadores postfijos tendern a devolver
copias de objetos (la correspondiente a nuestro objeto antes de ser
modificado).

C++/OOP: UN ENFOQUE PRCTICO Pgina 35/297


Resumiendo: la sobrecarga de operadores en C++ es una posibilidad, no
una necesidad, y debe ser usada juiciosamente. Debe intentar preservarse,
ante todo, el sentido intuitivo de la actuacin de los operadores en las
sobrecargas (sera digna del empalamiento transilvano la sobrecarga del
operador + para que actuara como una resta aritmtica). De cualquier
forma, si comenzamos a sobrecargar operadores en una clase, deberemos
definir un interfaz completo de sobrecargas, pues otra cosa confundira al
cliente de la clase. Imaginen que sobrecargamos el operador + en nuestra
clase Racional y que, sin embargo, definimos una funcin denominada resta
para implementar la sustraccin: el usuario de la clase fcilmente pensara
que al diseador se le practic una lobotoma en algn incierto momento de
su vida.

FUNCIONES AMIGAS DE UNA CLASE

Parece que se han sentado las bases para la descripcin completa de nuestra
clase, que quedara, ms o menos, de la siguiente guisa:

class Racional {
public:
boolean operator==( const Racional& );
boolean operator<( const Racional& );
// ...
Racional operator+( const Racional& );
Racional operator-( const Racional& );
Racional operator*( const Racional& );
// ...
Racional( int = 0, int = 1 );
~Racional();
private:
int numerador, denominador;
};

Recabemos ahora en un aspecto distinto sobre nuestro constructor.


Podramos considerar que el constructor es, en realidad, una funcin de
conversin de una pareja de enteros, y aun de un entero, en un objeto
distinto de tipo Racional. Posiblemente la idea podr ser mejor aprehendida
con el siguiente cdigo:

Racional suma, uno( 1 );


suma = uno + 7;

donde, como ya sabemos, la ltima lnea equivale a:

suma = uno.operator+( 7 );

Pero esta funcin slo admite un argumento de tipo Racional, y sin


embargo se le ha pasado un entero! Cmo responde el compilador?

C++/OOP: UN ENFOQUE PRCTICO Pgina 36/297


Intentando convertir tal entero en un objeto de tipo Racional, tal y como
requiere el argumento de la funcin. Y cmo realizar tal conversin?
Intentando "construir" el objeto a partir del nmero entero. Y cmo ... ?
Usando del constructor de nuestra clase! En definitiva puede decirse que
ocurre lo siguiente: primero se crea un objeto temporal de tipo Racional
para seguidamente ser usado como argumento de la funcin operador:

Racional objetoTemporal( 7 ); // "construye" el racional '7/1'


suma = uno.operator+( objetoTemporal );

El objeto suma pasar, as, a detentar un valor interno de '8/1'. Ahora, circunstancialmente, podemos
apreciar la ventaja de haber declarado parmetros por defecto en nuestro constructor. Si no hubiera sido as no
tendramos forma de "convertir" un entero en un objeto Racional. Observamos, tambin, la conveniencia de
haber significado por la unidad el segundo argumento por defecto, pues la representacin interna del objeto
construido a partir de un solo entero se corresponde as con el concepto matemtico que subyace tras tal
representacin. Veamos, sin embargo, qu ocurre con la siguiente expresin:

int siete = 7;
Racional unMedio( 1, 2 ), suma;
suma = siete + unMedio;

Pues lo mismo que anteriormente! -podra pensar alguno-: como la suma es


conmutativa, tanto da 'x+y' que 'y+x'; se construir un Racional a partir del
entero y etc., etc. Error, error y otra vez error! Orientemos nuestro
pensamiento hacia los objetos y sus relaciones. Qu viene a decir, bajo este
punto de vista, la ltima lnea del cdigo expuesto? Pues que el objeto uno
enva el mensaje operator+ (suma) al objeto siete de tipo int, el cual dispondr de un mtodo para responderlo,
que presumiblemente generar un nuevo objeto que enviar el mensaje operator= al objeto suma. Pero, el
objeto de tipo int no dispone de ningn tal mtodo!, por lo que el compilador sealar como errnea la expresin.
Veamos, no obstante, la explicacin funcional: de parecida forma a lo ya visto, la ltima lnea del cdigo equivale
a la siguiente:

suma = siete.operator+( unMedio );

Y es claro que un objeto de tipo int no soporta tal funcin. La suma


algebrica no es, pues, una operacin conmutativa? Sin duda, pero no as la
funcin operator+ de nuestra clase, pues tal y como la hemos implementado
el sumando a la derecha del operador debe, por fuerza, ser un objeto ya
construido de tipo Racional, por lo que no ha lugar a conversin posible
alguna. Y, bien, aqu se acaban nuestras alegras? No, ciertamente. Slo
debemos reconsiderar nuestro planteamiento del esquema. Dado que el
problema viene dado por el hecho que la funcin operator+ es una funcin
miembro de una clase, la solucin ms evidente radicara en que tal funcin
no fuera miembro de ninguna clase. Qu tendramos as? Pues una funcin
global, cuya declaracin, en nuestro caso, revistira la siguiente apariencia:

Racional operator+( const Racional&, const Racional& );

De esta forma, cuando el compilador se encuentre con el cdigo anterior (la


suma infija de un entero y un Racional), aplicar la funcin global,

C++/OOP: UN ENFOQUE PRCTICO Pgina 37/297


convirtiendo primero el entero en Racional. Entonces, debemos codificar
una funcin miembro y otra global para cada operador? Se produciran, en
este caso, errores por ambigedad en la aplicacin, para una determinada
expresin, de una u otra funcin? Bien, vayamos por partes. Si
definiramos, por ejemplo, el operador + como funcin miembro y, a la
vez, como funcin global, con sus respectivos argumentos de tipo const
Racional&, veramos que el cdigo compila sin problemas, pues son
funciones distintas (ni siquiera se trata de una sobrecarga). Pero -podra
pensar el lector-, como se decidira qu funcin utilizar si se trata, por
ejemplo, de una suma de Racionales, algo con lo que las dos funciones
parece que encajan bien? De acuerdo: esta es una pregunta interesante.
Veamos cmo reaccionara el compilador ante el siguiente cdigo:

int varint;
Prueba uno;
const Prueba dos,tres;
dos + tres; // funcin global
dos + uno; // funcin global
uno + tres; // funcin miembro
uno + dos; // funcin miembro
uno.operator+(dos); // funcin miembro
uno + varint; // funcin miembro
varint + dos; // funcin global
uno + 1; // funcin miembro
2 + dos; // funcin global

Quiz el lector se pregunte: no hay ambigedades? cul es el mtodo de


resolucin empleado por el compilador? Emprendamos un rpido tour por
los recovecos del lenguaje.

Cuando el compilador se encuentra con una expresin del tipo a @ b, donde @ es


un operador y donde bien a, bien b, bien ambos a y b son objetos instancias de clases, hay tres tipos de
funciones a ser consideradas:

- operadores predefinidos @, aplicados sobre tipos predefinidos y/o


instancias de clases susceptibles de ser convertidas a tipos
predefinidos, implcita o explcitamente.
- funciones miembros del tipo operator@(...), cuando a sea una instancia de
clase, pertenecientes a la clase a cuyo tipo a corresponde.
- funciones no-miembros del tipo operator@(...).

las cuales, a pesar de lo que pudiera pensar el lector, observan la misma


precedencia a efectos del procedimiento de resolucin que estamos
detallando.

Seguidamente entra en escena el siguiente esquema secuencial, conocido


como la regla de interseccin:

- se desechan las funciones que no puedan ser usadas, por su nmero


o tipo de argumentos, para resolver la llamada.

C++/OOP: UN ENFOQUE PRCTICO Pgina 38/297


- si no existe al menos una funcin que "encaje", se produce un error
y termina la secuencia.
- metafricamente hablando, se abren dos especie de "bolsas", futuras
contenedoras de funciones que puedan encajar con nuestra
expresin: una que llamaremos "bolsaIzquierda" y otra
"bolsaDerecha".
- se buscan las funciones que mejor encajen con el primer argumento
(atencin: no las que encajen, sino las que mejor lo hagan), y el
conjunto de ellas se introduce en lo que habamos llamado
"bolsaIzquierda".
- se buscan las funciones que mejor encajen con el segundo
argumento, insertando el conjutno de ellas en la denominada
"bolsaDerecha".
- se crea una nueva "bolsa", que denominaremos "bolsaInterseccion",
representativa del conjunto interseccin de la "bolsaIzquierda" y la
"bolsaDerecha".
- si la "bolsaInterseccion" contiene ms de una funcin, la llamada
ser calificada como ambigua, y la secuencia acabar.
- si la "bolsaInterseccion" contiene nicamente una funcin, y esta
funcin encaja mejor, en al menos un argumento de nuestra
expresin, que el resto de las funciones de las bolsas "bolsaDerecha"
y "bolsaIzquierda", entonces la llamada se resolver a favor de esta
funcin.
- en caso contrario (o sea, si la funcin encontrada no es la mejor
funcin de encaje), entonces se produce un error por ambiguedad en
la llamada.

Bien, pero cmo se determina cundo una determinada funcin se


corresponde con uno de los mejores encajes (best matchings) posibles?
Pues aplicando las cinco reglas de resolucin que tuvimos ocasin de revisar
cuando avistamos la sobrecarga de funciones, detalladas en la seccin 13.2
de ARM : si el "encaje" de una funcin se ajusta a una regla con precedencia
sobre la que se ajustara el "encaje" de otra funcin, aqulla primera ser un
mejor encaje que esta ltima.

Antes de revisar el ejemplo propuesto en este mismo pargrafo, debemos


indicar que, de acuerdo con lo establecido en la seccin 13.2 de ARM,
pginas 316-317, para propsitos de encaje, una funcin miembro
no-esttica se considerar como una funcin global con un argumento extra,
el cual deber encajar con el primer operando del operador sobrecargado y
que ser bien del tipo a& (para objetos instanciaciones de la clase a) en su
caso ms general, bien de los tipos const a& o volatile a& para funciones
miembros const y volatile repectivamente. Bueno, esto puede parecer
confuso, as que lo mejor ser que lo veamos en la prctica, repasando bajo
estas nuevas luces el ejemplo anterior.

C++/OOP: UN ENFOQUE PRCTICO Pgina 39/297


Tenemos, en principio, unas cuantas lneas de cdigo con operadores infijos
de suma, por lo que, desechando las dems funciones, encontramos dos
prototipos que podran encajar con tales expresiones:

// funcin global
Racional operator+( const Racional&, const Racional& );
clase Racional {
public:
// funcin miembro
Racional operator+( const Racional& );
};

donde, nicamente a efectos de encaje (match), la funcin miembro, con


arreglo a la regla expuesta, equivale a la siguiente funcin global:

Racional operator+( Racional&, const Racional& );

Examinemos ahora la primera expresin, en la que se suman dos objetos


constantes de tipo Racional. Cul de estas dos funciones encaja mejor con
respecto al primer operando? Como el segundo argumento de nuestras
funciones es el mismo, nicamente nos deberemos preocupar del primer
argumento. Al ser el primer operando un objeto constante, el mejor encaje
corresponder a la funcin global (que meteremos en la bolsaIzquierda).
Qu funcin encaja mejor, seguidamente, desde el punto de vista del
segundo operando? Ambas funciones, pues en ambas el segundo
argumento es una referencia a un objeto Racional constante, como constante
es el segundo operando (meteremos en la bolsaDerecha las dos funciones).
Qu contendr la bolsa Interseccin? Una nica funcin: la global. El
resultado coincide, pues, con el expuesto en el ejemplo.

Veamos otra expresin: la que suma un objeto Racional a un entero situado


a la derecha del operador. Con respecto al primer operando, el mejor encaje
lo proporciona la funcin miembro, pues, siendo el objeto no constante, se
prefiere un ajuste exacto a una conversin trivial de Racional& a const
Racional&, tal y como requerira la funcin global. Con respecto al segundo
operando, ambas funciones poseen el mismo rango de mejor encaje. La
interseccin? nicamente la funcin miembro.

Vemos, pues, que al diferir nuestras funciones nicamente en el "primer"


argumento, si el primer operando de la expresin no es constante la mejor
opcin ser la funcin miembro, y la funcin global si es constante. Por
supuesto la mejor opcin ser siempre la funcin global en el caso de un int
(o double o float) como primer operando. Sera un buen ejercicio para el
lector la comprobacin expresa de todas las expresiones.

Qu pasara, sin embargo, si calificramos nuestra funcin miembro como


const? Pues que, a efectos de encaje, el primer argumento de la funcin
habra pasado a ser const Racional&, con lo que al compilar el cdigo
obtendramos seis errores por ambigedad (todas las expresiones de suma a

C++/OOP: UN ENFOQUE PRCTICO Pgina 40/297


excepcin de las que disponen de un primer operando predefinido). El lector
puede experimentar, igualmente, con los errores que apareceran si, por
ejemplo, los argumentos de la funcin global (el primero, el segundo o
ambos) dejaran de ser constantes.

Pero no perdamos el hilo del relato: toda esta explicacin ha venido a cuento
de la necesidad de definir una funcin global para evitar algunas de las
incoveniencias de las funciones miembros operadores. Notamos que pueden
coexistir ambas versiones, pero que esto no tiene razn de ser y, si no
afinamos mucho, puede conducirnos a problemas de ambigedad. Qu
hacemos entonces? Pues suprimir la funcin miembro y dejar nicamente la
funcin global, naturalmente.

Hemos solucionado todos nuestros problemas? Es posible. Abordemos un


intento de definicin de nuestra funcin global:

Racional operator+( const Racional& lhs, const Racional& rhs )


{
Racional temp( lhs.numerador * rhs.denominador +
lhs.denominador * rhs.numerador,
lhs.denominador * rhs.denominador );
return temp;
}

Bueno, en apariencia parece suficiente. Es correcto, pues? No! Y el lector, a


estas alturas, ya debera conocer la razn. El algoritmo de suma de
racionales es, en esencia, correcto, pero examinemos detenidamente el
cdigo del cuerpo de la funcin: se construye un objeto temporal usando del
constructor de dos argumentos de la clase, para lo que se opera
algebricamente con los datos internos (numerador y denominador) de cada
uno de los objetos, accedidos a travs de la notacin '.' (dot). Y bien? Pues
que un objeto no puede acceder directamente a sus miembros privados
(como lo son numerador y denominador)! El compilador flagelar como
errneo el anterior cdigo. Qu hacer, entonces, para salvar esta nueva
traba? Siguiendo el esquema de ocultacin de la informacin, lo ms
evidente sera dotar a nuestra clase de sendas funciones miembros
(posiblemente inline) pblicas que "accedieran" a los datos privados.
Probemos:

class Racional {
public:
int num() const { return numerador; }
int denom() const { return denominador; }
private:
int numerador, denominador;
// sigue resto descripcin clase
};

De esta manera nuestra funcin global podra ser re-escrita de la siguiente


(correcta) forma:

C++/OOP: UN ENFOQUE PRCTICO Pgina 41/297


Racional operator+( const Racional& lhs, const Racional& rhs )
{
Racional temp( lhs.num() * rhs.denom()
+ lhs.denom() * rhs.num(),
lhs.denom() * rhs.denom() );
return temp;
}

As que hemos llegado, por fin, al desenlace de nuestros problemas?


Bueno, lo normal es que, efectivamente, aqu acabe el proceso. Pero
reexaminemos lo hecho: hemos creado unas funciones de acceso
convenientes para definir nuestra funcin global, pero al declararlas en la
seccin public de nuestra clase cualquier cliente podra acceder a ellas.
Preguntmonos: es aceptable que cualquier cliente acceda y opere por
separado con el numerador y el denominador de nuestros objetos
Racionales? Coherentemente, no. Si pensamos en la conceptualizacin de
tales objetos podremos apreciar que vienen definidos por la relacin entre
numerador y denominador, y no por los valores concretos de stos. De
hecho, como es bien conocido, una fraccin (que podemos denominar
reducida o simplificada) representa una serie infinita de fracciones mltiplos
de la misma. Vemos, pues, que los valores concretos del numerador y del
denominador no son, en absoluto, relevantes. Es ms, podra resultar
incluso peligroso permitir que ciertos clculos del cliente se basaran en tales
voltiles valores. Podramos, incluso, sostener la conveniencia de una
funcin interna (de acceso private):

class Racional {
private:
Racional& simplificar();
// sigue descripcin de clase
};

de tal forma que pudiera ser usada por constructores, operadores y, en


general, funciones miembros, para ofrecer as siempre una representacin
interna adecuada de nuestros objetos. En qu afecta esto a nuestra funcin
global? En nada, pues esta nueva funcin es privada (no tiene sentido dejar
tal herramienta en manos de los clientes de la clase, pues as podramos
cambiar su nombre posteriormente, o aun el esquema de representacin de
nuestros objetos), por lo que no podr ser accedida desde la funcin global.
Bueno, en un continuo vaiven, hemos vuelto a caer en el pozo de la
confusin.

Recapitulemos: con una funcin miembro tenemos el problema de que


ciertas expresiones no compilan como sera de esperar, mientras que con
una funcin global nos encontramos con serias restricciones de acceso a la
informacin interna de los objetos. Necesitamos lo mejor de ambos
mundos. Y aqu es donde entran en escena las funciones friends (amigas)

C++/OOP: UN ENFOQUE PRCTICO Pgina 42/297


Un amigo es una persona con la que se comparten las intimidades de uno.
De igual forma una funcin amiga (friend) es, sencillamente, una funcin
global con acceso a los miembros privados de una o ms clases. De qu
clases? Pues de las clases amigas de la misma. Y cmo ...? Bueno, para
que una determinada funcin se "amigue" con una o ms clases, lo nico
que hay que hacer es declarar tal funcin dentro del protocolo de descripcin
de estas clases antecedida por la palabra clave friend, como en el siguiente
ejemplo:

class Racional {
friend Racional operator+( const Racional&,
const Racional& );
friend boolean operator<=( const Racional&,
const Racional& );
// sigue resto descripcin de clase
};

Naturalmente, segn lo expuesto, una funcin slo puede hacerse amiga de


una clase en el momento de definir sta, pues de otra forma se vulnerara el
principio de ocultacin de la informacin: cualquiera podra acceder a los
datos internos de una clase declarando una funcin cualquiera como amiga
de la misma. La palabra clave friend se usa slo en la declaracin de la
funcin, pudiendo sta ser definida con normalidad:

Racional operator+( const Racional& lhs, const Racional& rhs )


{
Racional temp( lhs.numerador * rhs.denominador +
lhs.denominador * rhs.numerador,
lhs.denominador * rhs.denominador );
return temp.simplificar();
}

Vemos que el cdigo de construccin del objeto temporal es idntico al de


nuestro primer intento de funcin operador global, con la salvedad que esta
funcin, por el hecho de haber sido declarada amiga de la clase Racional,
puede acceder con total impunidad a la seccin private de la misma y a los
datos y funciones miembros que sta contenga. Podramos visualizar
grficamente la situacin pensando en que una relacin de amistad "abre" un
agujero en el esquema de proteccin de una clase procurando que, a efectos
prcticos y de acceso, la funcin se considere como un particular y
heterodoxo "miembro" de tal clase.

Incidentalmente podemos notar que en el anterior cdigo hemos hecho uso


de la funcin miembro simplificar() (tambin private, pero ahora accesible por nuestra funcin friend),
de manera que lo que ocurre en la ltima lnea es lo siguiente: al objeto temporal construido como suma de los dos
operandos de la funcin friend se le aplica un mtodo de simplificacin algebrica de la fraccin interna que lo
representa (diviendo numerador y denominador por el mximo comn divisor de ambos), mtodo que, como ya
vimos, devuelve una referencia al objeto ya simplificado. La sentencia de retorno, por ltimo, devuelve una copia
de tal objeto al mbito de llamada del operador. Por supuesto, esta funcin de simplificacin habra de ser usada,
tambin, en el cuerpo del constructor de la clase Racional, a fin de que, desde el principio, los objetos contengan
su representacin interna ptima.

C++/OOP: UN ENFOQUE PRCTICO Pgina 43/297


En qu seccin de una clase se declara una funcin friend? La verdad es
que se puede declarar en cualquiera: no olvidemos que las secciones de
acceso (controladas, como ya sabemos, por las etiquetas public, private y
protected) nicamente identifican el nivel de proteccin de los miembros de
la clase que contienen, y nuestra funcin friend, por no ser una funcin
miembro, no puede ser calificada con tal criterio. Lo habitual es, de cualquier
forma, declarar el bloque de funciones friend justo al principio de
descripcin de la clase, justo antes de declarar funcin o dato miembro
alguno.

Bien, ya hemos resuelto en gran medida nuestro problema con los


operadores infijos, pero debemos pensar lo siguiente: el cuidadoso esquema
de proteccin de las clases, tras el normalmente costoso proceso de diseo
que conlleva, es inmisericordemente vulnerado por las funciones friends, y
esto no parece muy lgico. Y, de hecho, no lo es. El lector deber seguir
siempre el siguiente esquema: intentar primero una funcin miembro,
despus una funcin global, y por ltimo, si lo anterior no es posible, una
funcin friend. Demasiadas funciones friend en una clase denotan bien una
cierta pobreza en su diseo bien una lectura desquiciada de ciertos textos
hindes.

SOBRECARGA DE LOS OPERADORES DE INSERCIN Y EXTRAC-


CIN

Qu mtodos deberemos usar para las operaciones de entrada/salida de


nuestros objetos racionales? Siendo coherentes, habremos de sobrecargar
los operadores '<<' y '>>'. Vemoslo con el operador de insercin:

#include <iostream.h> /* para poder declarar ms


adelante los tipos xstream */

class Racional {
public:
ostream& operator<<( ostream& );
// ...
};

O sea, el operador '<<', aplicado a uno de nuestros objetos racionales y a


un ostream (output-stream), tras ejecutar un cdigo que simplemente
imprimir los datos miembros de los objetos en bonita apariencia, devolver
una referencia a un ostream (presumiblemente al objeto predefinido cout),
de forma que la expresin envuelta pueda encadenarse y al ostream
retornado puedan serle insertados nuevos objetos. Correcto? Parece que
hemos pasado por alto una circunstancia que pronto nos ser muy familiar.
Cmo codificaramos una expresin con tal operador? Pues nuestro objeto
a la izquierda, tras l el operador, y seguidamente el stream. Algo as como:

C++/OOP: UN ENFOQUE PRCTICO Pgina 44/297


Racional miRacional( 1, 2 );
miRacional << cout; // horror: el mundo al revs

Qu es esto? No es la sintaxis normal a que estamos acostumbrados con


los streams. No es tampoco, sin embargo, una codificacin errnea:
nicamente es no-intuitiva:

miRacional << cout << " es mi nmero\n"; // 1/2 es mi nmero


miRacional << ( cout << "Mi nmero es " );// Mi nmero es 1/2

Recordemos ahora lo dicho anteriormente sobre el operador de


exponenciacin: en C++ las expresiones no-intuitivas, o que deban ser
cuidadosamente pensadas antes de escribirlas, han de evitarse como la peste
bubnica. Tenemos aqu, pues, una razn, distinta a la de los operadores
algebricos, para la re-escritura de nuestra funcin como no-miembro de la
clase, de forma que pueda ser variado el orden de los operandos.

Siguiendo la regla antes expuesta, primero debemos intentar una funcin


global. Apreciamos, empero, que la ms pausible implementacin

ostream& operator<<( ostream& os, const Racional& fraccion )


{
os << fraccion.numerador << '/'
<< fraccion.denominador << '\n';
return os;
}

vulnera el esquema de proteccin al intentar acceder, errneamente, a los


datos privados del objeto. La nica solucin sera, pues, declarar esta
sobrecarga (y la del operador de extraccin) como friends:

class Racional {
friend ostream& operator<<( ostream&, const Racional& );
friend istream& operator>>( istream&, const Racional& );
// sigue resto descripcin clase
};

A partir de ahora nuestros objetos de tipo Racional pueden ser insertados


con naturalidad en una cadena de impresin de objetos de otros tipos, o
bien pueden ser extrados de un istream con la misma facilidad sintctica
que un float o un char.

OPERADORES DE CONVERSIN

Ya hemos visto que, merced al constructor y a travs de funciones


miembros, globales y friends, se genera una suerte de "conversin" desde
tipos predefinidos a el tipo Racional. De esta manera, en la prctica,
podemos usar un long, un int o un float en cualquier expresin dondequiera
que es esperado un Racional. Lo que parece perfecto, pero, puede hacerse

C++/OOP: UN ENFOQUE PRCTICO Pgina 45/297


a la inversa? O sea, podemos intrumentar un procedimiento que permita
usar un Racional dondequiera que se espera un long o un double? En
definitiva, buscamos "algo" que permita codificar:

Racional unoRacional( 1 );
float unoFloat = unoRacional; // error

La solucin es clara: solamente debemos dotar a la clase Racional de


mtodos de conversin para trocar el tipo de sus objetos, y esto se realizar
mediante lo que se denominan operadores de conversin. Adelantar, para
avanzar rpido, un ejempo:

class Racional {
public:
operator float() {
return float( numerador / denominador );
}
// sigue resto descripcin de clase
};

Hemos definido una nueva funcin miembro de nuestra clase, con la especial
sintaxis de los operadores, sin argumentos y sin tipo alguno de retorno, lo
cual es lgico si pensamos que, como operador de conversin, habr de
convertir un objeto Racional devolviendo, al fin del proceso, un float, por lo
que ya se anuncia el tipo de retorno. De esta manera ya podramos compilar
sin problemas el anterior cdigo.

Con cierta coherencia, los operadores podrn aplicarse para la conversin a


tipos predefinidos y clases. Esto es, contando con que, por ejemplo,
hubiramos ya declarado la clase Complejo, podra definirse un operador de
conversin de un objeto Racional a un objeto Complejo:

#include <complex.h>

class Racional {
public:
operator float();
operator complex();
// ...
};

Parece as que, con las sobrecargas de operadores por un lado y los


operadores de conversin por otro, nuestros objetos Racionales se
comportarn adecuadamente sea cual sea su posicin o uso en una
expresin, asemejndose en tal aspecto a los objetos de tipos predefinidos.
Bueno, lo parece pero no lo es. Veamos qu ocurre cuando el compilador se
encuentra algo parecido a lo siguiente:

Racional unMedio( 1, 2 );
2.5 + unMedio;

C++/OOP: UN ENFOQUE PRCTICO Pgina 46/297


Recapacite el lector: qu devuelve esta ltima lnea? un float o un
Racional? acaso compilar? Bien, recontemos nuestro arsenal: tenemos una
funcin friend para el operador de suma, que devuelve un Racional, y
tenemos tambin un operador de conversin que, trocando el Racional en
float y sumndole el primer valor, causara que la expresin devolviera un
float. Para resolver esta situacin el compilador utiliza la Regla de
Interseccin, que ya detallamos al comentar las funciones amigas: con
respecto al primer argumento (un float), el mejor encaje lo proporciona el
operador de suma predefinido (tras la aplicacin del operador de conversin
a float); con respecto al segundo argumento el mejor encaje es el de la
funcin friend que sobrecarga el operador de suma. La interseccin de
ambos conjuntos es nulo, por lo que el compilador flagelar la expresin de
suma como un error por ambigedad.

Pero bueno, exclamar el hastiado lector, en C++ tras cada montculo


superado aparece una montaa mayor! Quiere esto decir que no podemos
usar de forma concurrente las sobrecargas de operadores y los operadores
de conversin? No exactamente. Imaginemos que aadimos a nuestra lista
de funciones amigas las siguientes:

class Racional {
friend Racional operator+( float, const Racional& );
friend Racional operator+( const Racional&, float );
friend Racional operator+( const Racional&,
const Racional& );
// ...
public:
operator float();
// ...
};

Ahora la aplicacin de la Regla de Interseccin a la anterior expresin de


suma generar un resultado diferente. Los mejores encajes con respecto al
primer operando son la funcin operator+(float,const Racional&) y el operador predefinido de
suma de floats (gracias al operador de conversin a float). Los mejores encajes con respecto al segundo
operando los constituyen la funcin operator+(float,const Racional&) y la funcin operator+(const
Racional&,const Racional&). La interseccin ahora es, pues, la funcin operator+(float,const Racional&), con lo
que ya no se da la ambigedad anterior. Pero, y si quisiramos que el Racional operara como un float en tal
expresin? nicamente deberamos realizar un cast expreso de nuestro objeto:

float resultado = 2.5 + (float)unMedio; // Ok: no hay


// ambigedades

Se han resuelto entonces todas las ambigedades? El lector ya puede


suponer que eso sera demasiada felicidad. Veamos qu ocurre si
codificamos:

long prueba;
Racional dosTercios;

C++/OOP: UN ENFOQUE PRCTICO Pgina 47/297


float suma = prueba + dosTercios; // error: AMBIGUO!

Qu ha pasado aqu? Es fcil de ver: en la resolucin de la operacin de


suma mediante la aplicacin de la Regla de Interseccin, los mejores encajes
con respecto al segundo operando siguen siendo las dos funciones friend del
caso anterior, pero el mejor encaje con respecto al primer operando es... el
operador predefinido de suma para longs! La interseccin es vaca y, por
tanto, la llamada es ambigua. Por supuesto aqu caben varias posibles
soluciones. En caso que quisiramos que prevaleciera el uso del operador
predefinido, codificaramos lo siguiente:

float suma = int( prueba ) + dosTercios;

Podramos, por otra parte, en caso de desear el uso de la sobrecarga de


operadores, explicitar un cast a float para la variable long; o podramos
definir nuevas funciones friend en nuestra clase cubriendo todos los posibles
casos de operadores entre Racionales y tipos predefinidos. Pero la verdad es
que todo esto no parece demasiado apropiado. Se nos antoja que la
combinacin de sobrecargas de operadores y de operadores de conversin
no ajusta como debiera. Bien, esto es un hecho y los desarrolladores de
C++ deben aprender a vivir con l.

Caben, en principio, dos posibles planteamientos: bien suprimimos todas las


sobrecargas de operadores y operamos con conversiones, bien prescindimos
de las conversiones "peligrosas". Por mi parte, a pesar de las discutibles
ventajas de la primera opcin, decido, teniendo adems en mente que los
Racionales son un superconjunto de los enteros, mantener las sobrecargas.
No es deseable, empero, perder la capacidad de usar los Racionales como
tipos predefinidos. Podemos llegar a una solucin de compromiso? Bueno,
podemos dotar a nuestra clase de funciones miembros expresas de
conversin, del tipo convierteAEntero(), etc:

class Racional {
public:
int comoInt() {
return int( numerador / denominador );
}
int comoFloat() {
return float( numerador / denominador );
}
// etc, etc.
};

De esta forma ahora podramos codificar lo siguiente:

Racional dos( 2 ), tresQuintos( 3, 5 );


int alFinSuma = 3 + dos.comoInt();
float sumaDeFloats = tresQuintos.comoFloat() + 2.5;

C++/OOP: UN ENFOQUE PRCTICO Pgina 48/297


Como casi siempre, lo ptimo prevalece sobre lo mejor en C++. Esta
pequea revisin, por otro lado, de algunas "sutilezas" del lenguaje espero
haga comprender a los lectores la necesidad -imperiosa- de entender con
cierta profundidad los mecanismos del mismo. Y para esto hay que leer,
leer, leer. Y estudiar el ARM.

FUNCIONES MIEMBROS ESTTICAS

Como ya sabemos cada objeto de nuestra clase Racional posee una


representacin interna distinta, de forma que las funciones miembros, a
travs del puntero implcito this, quedan particularizadas y pueden acceder a
unos datos concretos correspondientes al objeto desde el que se llaman.
Esto es perfecto para unos datos internos que se suponen diferentes para
cada objeto, pero qu pasa si deseamos dotar a nuestros objetos de unos
datos compartidos por la totalidad de los otros objetos de la clase? o si
queremos implementar en un objeto un mtodo que no dependa de la
representacin interna del mismo? Bueno, ojeemos el horizonte.

Imaginemos que deseamos controlar el nmero de objetos de tipo Racional


activos en un momento dado. Hablamos, en definitiva, de una variable de
tipo int que podemos identificar como numeroDeRacionales. Corresponde
tal variable a la representacin interna de un objeto concreto?
Evidentemente no. Se trata entonces de una variable global? Pudiera ser.
De hecho si furamos consecuentes con el tradicional esquema de desarrollo
estructurado sta sera la representacin idnea. Pero pensemos que tal
variable nicamente tiene sentido para los objetos de nuestra clase, por lo
que dotarla de mbito global de programa es una accin similar a la del
novio que se compra cinco trajes para la ceremonia nupcial. En realidad tal
variable debera estar adscrita al mbito de nuestra clase Racional, con lo
que conseguiramos dos cosas por el precio de una: en primer lugar evitar
conflictos de nombres globales, y en segundo lugar poder aplicar el
esquema de ocultacin de la informacin a tal variable. Efectivamente no hay
ninguna razn para suponer que la variable numeroDeRacionales, a pesar de
poder ser compartida por todos los objetos, deba poseer acceso pblico, y
ms bien uno se da en pensar que en este caso la privacidad habra de ser
preservada. Muy bien, y como ... ? Pues declarando tal variable como un
dato miembro esttico (static) de nuestra clase:

class Racional {
private:
int numerador, denominador;
Racional& simplificar();
static int numeroDeRacionales;
public:
Racional( int = 0, int = 1 );
// ...
};

C++/OOP: UN ENFOQUE PRCTICO Pgina 49/297


A diferencia de lo que ocurre con el resto de datos miembros, la declaracin
de un dato miembro static no es una definicin. Esto es, el desarrollador
debe proveer una inicializacin o definicin expresa en algn lugar del
cdigo, preferentemente en el archivo en que se implementan las funciones
de la clase. La inicializacin, por otro lado, al igual que ocurre con las
variables globales estticas, nicamente puede realizarse una vez. Veamos
cmo:

int Racional::numeroDeRacionales = 0;

Bueno, este cdigo requiere algn comentario. En primer lugar vemos que
es necesario aplicar a nuestra variable el operador de resolucin de mbito,
lo cual es lgico, pues la visibilidad de sta se limita al mbito de la clase.
Podramos preguntarnos seguidamente: qu pasa con el nivel de acceso?
Esta variable es privada y, sin embargo la hemos inicializado con mbito
global de fichero. En realidad esta es una particularidad de los miembros
estticos de clases: el nivel de proteccin se refiere a las operaciones de
acceso y modificacin de los mismos, pero no afecta a su inicializacin o
definicin.

Cmo podra usarse este nuevo miembro de la clase? Lo que parece ms


claro es que el constructor (o constructores en otros casos) incremente en
una unidad el dato miembro esttico cada vez que se cree un objeto, a la vez
que el destructor minorar, igualmente, una unidad cada vez que se
destruya uno.

Racional::Racional( int num, int denom )


: numerador( num ), denominador( denom )
{
simplificar();
Racional::numeroDeRacionales += 1;
}

Racional::~Racional()
{
Racional::numeroDeRacionales -= 1;
}

Constructor y destructor pueden acceder al dato miembro esttico, a pesar


de ser privado, precisamente por ser funciones miembros de la clase. Pero
sigamos con el esquema de ocultacin de la informacin:
numeroDeRacionales es un dato miembro privado y, al igual que haramos
con otros miembros no-estticos, debemos proveer a la clase al menos de
una funcin de acceso a tal variable. Intentmoslo:

class Racional {
public:
static numeroDeObjetosRacionales() {
return Racional::numeroDeRacionales;
}

C++/OOP: UN ENFOQUE PRCTICO Pgina 50/297


// ...
};

Es imprescindible que una funcin que acceda a un miembro esttico sea


tambin esttica? No! Lo que s se cumple es, precisamente, lo contrario:
una funcin miembro no podr ser calificada como esttica si accede al
menos a un dato o funcin miembro no-esttica. Hay que entender que los
miembros estticos no forman parte de los objetos: los datos miembros
estticos se constituyen en objetos diferenciados y aparte, y las funciones
miembros estticas existen y pueden ser llamadas aunque no se hubiera
construido ningn objeto de la clase a que pertenecen. Por esta razn las
funciones miembros estticas no pueden acceder a los miembros de un
objeto (a excepcin de a los pblicos, naturalmente), y para verlo claro el
lector slo debe preguntarse: a los miembros de qu objeto? Pensemos
que la llamada a funciones miembros no estticas se realiza desde un
determinado objeto. Igualmente desde un objeto cualquiera se puede
acceder a una funcin miembro esttica, pero esto nicamente se debe a una
cuestin de coherencia sintctica: de hecho el objeto desde el que se accede
a tal funcin no es en absoluto importante (tanto es as que ni siquiera es
evaluado por el compilador) y constituye lo que se llama una extensin
dummy de completitud sintctica. Las funciones miembros estticas pueden
accederse tambin, y esto es lo ms normal y recomendable, directamente.
Vemoslo:

// acceso directo
cout << Racional::numeroDeObjetosRacionales();
Racional cualquiera;
// funcin miembro
cout << cualquiera.numeroDeObjetosRacionales();

Como el lector probablemente ya habr adivinado, el hecho que las


funciones miembros estticas no pertenezcan a ningn objeto fuerza a que
no contengan al puntero implcito this (a qu objeto apuntara?). Es ms:
cualquier invocacin expresa de tal puntero implcito en su cuerpo sera
calificada como un error. Lo ms adecuado para el novicio sera pensar
siempre en estas funciones como funciones globales estticas, aunque con
especiales niveles de visibilidad y acceso.

Los datos miembros estticos, desde el momento de su declaracin en el


protocolo de descripcin de una clase, pueden ser utilizados como valores en
listas de parmetros por defecto, as como pueden, por el mismo hecho que
su declaracin no es una definicin, ser ojetos de la clase de la que son
miembros.

Naturalmente los miembros estticos expuestos quiz no sean demasiado


apropiados para incorporarlos a nuestra clase, pues, en definitiva, se han
creado como ejemplificacin pedaggica de la seccin.

C++/OOP: UN ENFOQUE PRCTICO Pgina 51/297


UN EPLOGO DE COLUSIN

Hemos completado ya el diseo de la clase Racional? Parece que as es:


tras un incesante vaiven de problemas, dudas, indecisiones y sutilezas, el
interfaz de nuestra clase parece haber tomado una forma bien definida. El
lector podra pensar: "Ha sido ...(ourps!), pero las cosas han quedado
medianamente claras al final". Pero pongamos al lector en un brete,
detallando un planteamiento no visto: imaginemos que hacemos coexistir a
funciones operadoras globales y miembros. Cmo? He aqu un ejemplo:

// funcin global
Racional operator+( float, const Racional& );

class Racional {
public:
Racional operator+( const Racional&, const Racional& );
// ...
};

inline Racional operator+( float miFloat, const Racional& rhs )


{
return rhs.operator+( miFloat );
}

De esta manera la funcin global, definida inline (con lo que no se ocasiona


penalizacin en su llamada) soporta el nico caso problemtico que no
puede tratar bien la funcin operadora miembro, implementando una suerte
de conmutatividad forzada. Dejo como ejercicio al siempre amable lector la
comprobacin de la idoneidad o no de este planteamiento: su practicidad, si
ocasiona o no errores por ambigedad, su relacin con los operadores de
conversin, etc. Y es que en C++ hay que estar preparado para todo.

Otra sorpresita: realmente interesa una representacin interna de


numerador y denominador como ints? Yo creo que no. Si realmente
deseamos otorgar de operatividad prctica y flexibilidad a nuestra clase, una
representacin en longs sera ms apropiada. Cambiemos, pues, estos tipos.
Mucho trabajo? No! Pinsese que nicamente estamos completando el
interfaz de la clase, por lo que los cambios afectan slo a una pequea
porcin de cdigo: otra de las ventajas de separar interfaz e
implementacin.

INTERFAZ DE LA CLASE RACIONAL

Lo que sigue es un intento elemental de descripcin de la clase Racional,


teniendo en cuenta que se han suprimido grupos habituales de funciones
miembros, como los referidos a operaciones de exponenciacin, valor
absoluto, etc., de fcil implementacin por el lector.

#ifndef RACIONAL_H

C++/OOP: UN ENFOQUE PRCTICO Pgina 52/297


#define RACIONAL_H

// fichero: RACIONAL.H
// contenido: interfaz de la clase "Racional",
// representacion del conjunto matematico
// de los numeros racionales.
// autor: Ricardo Devis
// derechos: (C) INFO+ (1993)
// Este codigo puede copiarse libremente,
// con la unica restriccion de respetar
// el aviso de copyright.
// last mod: 03-06-93

#include <iostream.h>

typedef int boolean;

class Racional {

// operadores de entrada/salida
friend ostream& operator<<( ostream& os,
const Racional& );
friend istream& operator>>( istream& is, Racional& );
// operadores artimeticos
friend Racional operator+( const Racional&,
const Racional& );
friend Racional operator-( const Racional&,
const Racional& );
friend Racional operator*( const Racional&,
const Racional& );
friend Racional operator/( const Racional&,
const Racional& );
// operadores de comparacion
friend boolean operator==( const Racional&,
const Racional& );
friend boolean operator!=( const Racional&,
const Racional& );
friend boolean operator>( const Racional&,
const Racional& );
friend boolean operator<( const Racional&,
const Racional& );
friend boolean operator<=( const Racional&,
const Racional& );
friend boolean operator>=( const Racional&,
const Racional& );

public:
// constructor y destructor inline
Racional( long num = 0, long denom = 1 )
: numerador( num ), denominador( denom )
{
simplificar();
}
~Racional() {};

C++/OOP: UN ENFOQUE PRCTICO Pgina 53/297


// operadores unarios
Racional operator-();
Racional& operator+();
// operadores prefijos
Racional& operator++();
Racional& operator--();
// operadores postfijos
Racional operator++( int );
Racional operator--( int );
// operadores logicos
boolean operator!();
// operadores de asignacion
Racional& operator+=( const Racional& );
Racional& operator-=( const Racional& );
Racional& operator*=( const Racional& );
Racional& operator/=( const Racional& );
// funciones de conversion
float comoFloat();
long comoLong();
double comoDouble();
int comoInt();

private:
long numerador;
long denominador;
// funcion interna para simplificar fracciones
Racional& simplificar();
};

#endif /* RACIONAL_H */

IMPLEMENTACIN DE LA CLASE RACIONAL

A excepcin del constructor y el destructor (lastrados por un inters


eminentemente pedaggico), todas las dems funciones se definen en el
siguiente mdulo, incluso las inline. Note el lector que funciones como por
ejemplo el operador '!=' se aprovechan de la anterior definicin de su
operador complementario ('==') y de la caracterstica de inlining, trocando el
cdigo ms intuitivo.

// fichero: RACIONAL.CPP
// contenido: implementacion de la clase "Racional",
// representacion del conjunto matematico
// de los numeros racionales.
// autor: Ricardo Devis
// derechos: (C) INFO+ (1993)
// Este codigo puede copiarse libremente,
// con la unica restriccion de respetar
// el aviso de copyright.
// last mod: 03-06-93

C++/OOP: UN ENFOQUE PRCTICO Pgina 54/297


#include "racional.h"

ostream& operator<<( ostream& os, const Racional& rhs )


{
os << rhs.numerador << '/' << rhs.denominador;
return os;
}

istream& operator>>( istream& is, Racional& rhs )


{
char div;
long num, denom;
is >> num >> div >> denom;
if ( is )
rhs = Racional( num, denom );
return is;
}

Racional operator+( const Racional& lhs, const Racional& rhs )


{
Racional temp( lhs.numerador * rhs.denominador
+ lhs.denominador * rhs.numerador,
lhs.denominador * rhs.denominador );
return temp.simplificar();
}

Racional operator-( const Racional& lhs, const Racional& rhs )


{
Racional temp( lhs.numerador * rhs.denominador
- lhs.denominador * rhs.numerador,
lhs.denominador * rhs.denominador );
return temp.simplificar();
}

Racional operator*( const Racional& lhs, const Racional& rhs )


{
Racional temp( lhs.numerador * rhs.numerador,
lhs.denominador * rhs.denominador );
return temp.simplificar();
}

Racional operator/( const Racional& lhs, const Racional& rhs )


{
Racional temp( lhs.numerador * rhs.denominador,
lhs.denominador * rhs.numerador);
return temp.simplificar();
}

boolean operator==( const Racional& lhs, const Racional& rhs )


{
return ( lhs.numerador * rhs.denominador
== lhs.denominador * rhs.numerador );
}

C++/OOP: UN ENFOQUE PRCTICO Pgina 55/297


inline boolean operator!=( const Racional& lhs,
const Racional& rhs )
{
return !( lhs == rhs );
}

boolean operator>( const Racional& lhs, const Racional& rhs )


{
return ( lhs.numerador * rhs.denominador
> lhs.denominador * rhs.numerador );
}

boolean operator<( const Racional& lhs, const Racional& rhs )


{
return ( lhs.numerador * rhs.denominador
< lhs.denominador * rhs.numerador );
}

boolean operator<=( const Racional& lhs, const Racional& rhs )


{
return ( lhs.numerador * rhs.denominador
<= lhs.denominador * rhs.numerador );
}

boolean operator>=( const Racional& lhs, const Racional& rhs )


{
return ( lhs.numerador * rhs.denominador
>= lhs.denominador * rhs.numerador );
}

Racional Racional::operator-()
{
Racional temp( -numerador, denominador );
return temp;
}

Racional& Racional::operator+()
{
return *this;
}

Racional& Racional::operator++()
{
numerador += denominador;
return *this;
}

Racional& Racional::operator--()
{
numerador -= denominador;
return *this;
}

C++/OOP: UN ENFOQUE PRCTICO Pgina 56/297


Racional Racional::operator++( int )
{
Racional temp = *this;
numerador += denominador;
return temp;
}

Racional Racional::operator--( int )


{
Racional temp = *this;
numerador -= denominador;
return temp;
}

inline boolean Racional::operator!()


{
return !( int( numerador ) );
}

Racional& Racional::operator+=( const Racional& rhs )


{
numerador = numerador * rhs.denominador
+ denominador * rhs.numerador;
denominador = denominador * rhs.denominador;
simplificar();
return *this;
}

Racional& Racional::operator-=( const Racional& rhs )


{
numerador = numerador * rhs.denominador
- denominador * rhs.numerador;
denominador = denominador * rhs.denominador;
simplificar();
return *this;
}

Racional& Racional::operator*=( const Racional& rhs )


{
numerador = numerador * rhs.numerador;
denominador = denominador * rhs.denominador;
simplificar();
return *this;
}

Racional& Racional::operator/=( const Racional& rhs )


{
numerador = numerador * rhs.denominador;
denominador = denominador * rhs.numerador;
simplificar();
return *this;
}

float Racional::comoFloat()

C++/OOP: UN ENFOQUE PRCTICO Pgina 57/297


{
return (float)numerador / (float)denominador;
}

double Racional::comoDouble()
{
return (double)numerador / (double)denominador;
}

long Racional::comoLong()
{
return numerador / denominador;
}

int Racional::comoInt()
{
return (int)numerador / (int)denominador;
}

Racional& Racional::simplificar()
{
int minimo;
int mcd = 1; // maximo comun divisor
if ( numerador > denominador )
minimo = denominador;
else
minimo = numerador;
for ( int i = 1; i <= minimo; i++ )
if ( ( numerador % i == 0 )
&& ( denominador % i == 0 ) )
mcd = i;
numerador /= mcd;
denominador /= mcd;
return *this;
}

MANEJO ELEMENTAL DE OBJETOS DE TIPO RACIONAL

El siguiente cdigo es un muy elemental ejemplo de las posibilidades


prcticas de uso de distintos objetos de tipo Racional, y ni siquiera agota los
mtodos definidos en nuestra clase.

// fichero: RACIOTST.CPP
// contenido: ejemplos de uso de la clase "Racional",
// autor: Ricardo Devis
// derechos: (C) INFO+ (1992)
// Este codigo puede copiarse libremente,
// con la unica restriccion de respetar
// el aviso de copyright.
// last mod: 03-06-93

#include "racional.cpp"

C++/OOP: UN ENFOQUE PRCTICO Pgina 58/297


#include <iostream.h>

#define nl '\n'

int main( int, char** )


{
Racional resultado, dosTercios( 2, 3 ),
cincoMedios( 10, 4 );

cout << "MUESTRA EFECTO DE CONSTRUCTORES: " << nl;


cout << "resultado => "
<< resultado << nl;
cout << "dosTercios( 2, 3 ) => "
<< dosTercios << nl;
cout << "cincoMedios( 10 ,4 ) => "
<< cincoMedios << nl << nl;

cout << "MUESTRA SOBRECARGA DE OPERADORES: " << nl;


cout << "dosTercios + cincoMedios => "
<< dosTercios + cincoMedios << nl;
cout << "2 + dosTercios => "
<< 2 + dosTercios << nl;
cout << "dosTercios - 2 => "
<< dosTercios - 2 << nl;
cout << "3 / cincoMedios => "
<< 3 / cincoMedios << nl;
cout << "cincoMedios * 3 => "
<< cincoMedios * 3 << nl;
cout << "-cincoMedios => "
<< -cincoMedios << nl;
cout << "dosTercios - cincoMedios => "
<< dosTercios - cincoMedios << nl;
cout << "dosTercios + -cincoMedios => "
<< dosTercios + -cincoMedios << nl;
cout << "+cincoMedios => "
<< +cincoMedios << nl;
cout << "++cincoMedios => "
<< ++cincoMedios << nl;
cout << "cincoMedios-- => "
<< cincoMedios-- << nl;
cout << "resultado = ++cincoMedios =>resultado = ";
cout << (resultado = ++cincoMedios)
<< " y cincoMedios = " << cincoMedios << nl;
cout << "resultado = cincoMedios-- => resultado = ";
cout << (resultado = cincoMedios--)
<< " y cincoMedios = " << cincoMedios << nl;
cout << "resultado += cincoMedios => "
<< (resultado += cincoMedios) << nl;
cout << "resultado += 2 => "
<< (resultado += 2 ) << nl;
cout << "resultado -= dosTercios => "
<< (resultado -= dosTercios) << nl;
cout << "resultado *= cincoMedios => "
<< (resultado *= cincoMedios) << nl;

C++/OOP: UN ENFOQUE PRCTICO Pgina 59/297


cout << "resultado /= dosTercios => "
<< (resultado /= dosTercios) << nl << nl;

cout << "MUESTRA CONVERSION A TIPOS PREDEFINIDOS: "


<< nl;
cout << "cincoMedios.comoInt() + 1 => "
<< (cincoMedios.comoInt() + 1) << nl;
cout << "cincoMedios.comoFloat() + 1.5 => "
<<(cincoMedios.comoFloat() + 1.5) << nl;
cout << "cincoMedios.comoDouble() + 2.5 => "
<<(cincoMedios.comoDouble() + 2.5) << nl;
cout << "cincoMedios.comoLong() + 10 => "
<< (cincoMedios.comoLong() + 10) << nl;

return 0;
}

C++/OOP: UN ENFOQUE PRCTICO Pgina 60/297


DE LA HERENCIA
9
Y DERIVADOS

A estas alturas ya hemos revisado muchos de los tpicos del lenguaje C++
que nos habrn de permitir disear, al principio con inevitable candidez,
nuestras primeras clases. De hecho, como ya vimos en la construccin de la
clase Racional, la tradicional metodologa de programacin ha sufrido un
vuelco (otra cuestin sera si hemos derramado algo realmente importante).
Quiere esto decir, pues, que ya estamos programando con tcnicas de
orientacin-a-objetos? Bueno, un descorazonador halito de honradez me
impide decir, en puridad, que s. La verdad es que, si recordamos bien, las
propiedades (o, ms bien, facilidades) que caracterizan a un OOPL son:
abstraccin, encapsulacin, herencia y polimorfismo. En la mera
construccin de clases hemos involucrado solamente los conceptos de
abstraccin y encapsulacin, as como una muy rudimentaria aproximacin
al polimorfismo (mediante la sobrecarga de operadores y funciones). En
C++, sin embargo, los mecanismos de OOP tienen su principal apoyo en la
caracterstica denominada "herencia" y que en C++ se implementa mediante
la derivacin de clases: en ella se basa el mecanismo polimrfico del
lenguaje, constituido por las funciones virtuales; en ella se realzan y tienen
pleno sentido, tambin, las propiedades de abstraccin y encapsulacin.
Apreciamos, as, que la herencia es el ojo del huracn de la OOP con
respecto de C++ . No debemos olvidar, con todo, que, adems de C++ y
por raro que parezca, existen otros OOPL's en los que no todo tiene su
exacta correspondencia: en el lenguaje SELF, por ejemplo, la herencia se
sustituye por la delegacin, una interesantsima propiedad (de posible
implementacin "manual" en C++), cuya explicacin en detalle sobrepasa
(cmo no?) los lmites de esta introduccin. Bueno, derivemos a lo prctico
y heredaremos facilidad de codificacin.

C++/OOP: UN ENFOQUE PRCTICO Pgina 61/297


UN PRIMER ACERCAMIENTO A LA HERENCIA

C++/OOP: UN ENFOQUE PRCTICO Pgina 62/297


Qu es la herencia? El acto por el que un ente transfiere parte o la totalidad
de sus haberes o componentes a otro u otros entes. Se puede hablar de
herencia en sistemas de objetos? Naturalmente! De la misma -y a veces
desafortunada- forma que los hijos poseen una parte gentica de sus padres,
los objetos a veces poseen una parte "heredada" (y lo cierto es que esta
visualizacin se corresponde con la realidad fsica de la herencia simple).
Un objeto puede, pues, heredar propiedades de otro u otros? No! Como ya
se ha recordado repetidas veces, C++ es un lenguaje cuya esencia dual est
representada por las clases y los objetos, limitndose estos ltimos a ser
meras instanciaciones de aqullas. O sea, propiedades como abstraccin,
encapsulacin, etc., se aplican a las clases, y no a los objetos. La herencia
es, por tanto, una propiedad nicamente circunscrita a las clases.
Naturalmente los objetos, instanciaciones de las clases inmersas en el
sistema de derivacin, se beneficiarn de esta circunstancia, pero el lector
debe tener muy claro que lo que se hereda no son los valores especficos de
los miembros de uno o ms objetos, sino las "estructuras vacas" que
conforman las clases a que pertenecen. Como se implementa la herencia en
C++? Mediante el mecanismo conocido como derivacin de clases: a travs
de la aplicacin de la sintaxis que seguidamente veremos, en la definicin de
una clase se puede "enlazar" sta a otra ya definida, consiguiendo que
aqulla se constituya en lo que se denomina clase base, mientras que sta
pasa a ser una clase derivada, heredando, junto con ciertos protocolos de
acceso, distintos miembros (datos y funciones) de la clase base. Cualquier
clase se puede "hacer derivar" de otra dada? Pues s: con la exclusin de la
autoderivacin, en el momento de definir una clase se puede expresamente
indicar la voluntad de desear heredar de una o ms clases, con la nica
precaucin de que estn ya definidas. Una o ms? Entonces una clase
derivada puede depender de ms de una clase base? Efectivamente! La
herencia puede ser simple (derivacin de una sola clase base) o mltiple
(derivacin de distintas clases base). C++ naci21, de hecho, solamente con
la herencia simple, incorporndose la mltiple a partir de AT&T 2.0. En lo
que sigue hablaremos, bsicamente, de la herencia simple, aunque tambin
veremos algunos rudimentos de uso de la herencia mltiple (como tambin
repasaremos muchos de sus problemas). Bueno, pero para qu demonios
sirve la herencia? Muy sencillo: para permitir la tan cacareada reutilizacin
del cdigo. En efecto: la porcin fsica, correspondiente a cada una de las
clases base, que se aade a la clase derivada es el resultado de la
reutilizacin -sin tener que reescribirlo- de parte del cdigo usado en cada
una de las clases base. Es entonces la derivacin de clases nicamente una
instrumentacin de C++ para procurar la reutilizacin del cdigo? Bien, la
verdad es que estamos considerando la cuestin desde un ngulo errneo:
el mecanismo de derivacin da lugar a jerarquas ms o menos complejas de

21
Volvemos aqu al gastado tema de la prevalencia al considerar los posibles
estndares del lenguaje. C++ naci, de hecho, como una implementacin
particular de AT&T, independientemente de que ARM sirviera despus como
documento base para el comit ANSI C++. No cansar, pues, ms al lector.

C++/OOP: UN ENFOQUE PRCTICO Pgina 63/297


clases, procurando relaciones de distinto tipo y cualificacin entre las mismas
y posibilitando, as, la asuncin, para el subsistema as formado, de los
distintos comportamientos que conforman lo que se conoce como OOP.
Naturalmente que se reutiliza cdigo, aunque es posible pensar en un
particular esquema de derivacin en el que no se reutilice una sla lnea ya
escrita. La derivacin posibilita, adems, la implementacin completa de la
cualidad de polimorfismo (que parcialmente vimos aplicada en la sobrecarga
de funciones) mediante las funciones virtuales. Existen distintos tipos de
derivacin de clases? Ciertamente; como distintas formas hay de testar. El
tipo de derivacin depende, nicamente, del nivel de acceso -o de restriccin
del acceso- elegido para la misma. Existen, pues, tres tipos de derivacin:
pblica (public), protegida (protected) y privada (private). Esto recordar al
lector la cualificacin de acceso de las secciones de una clase (siendo parejo,
en el fondo, el funcionamiento en ambos estadios), que siempre hemos visto
nicamente como public y private, pero que ahora podemos ampliar
formalmente tambin a protected. La seccin protegida de una clase, a los
solos efectos de su consideracin como clase aislada no participante en un
esquema de derivacin, se considerar como privada: esto es, las
instanciaciones de tal clase no tendrn acceso a lo en ese bloque contenido.
Esto cambia cuando de tal clase se derivan otras, pero ya lo veremos con
ms detalle en su momento.

Bien, contestadas las preguntas bsicas comenzaremos la revisin de la


herencia en C++: primero la sintaxis; luego el uso; ms tarde el abuso;
seguir la racionalidad; y, para terminar, el ejemplo.

HERENCIA SIMPLE: UN LOBO CON PIEL DE MELOCOTN

Como ya sabemos, si en la definicin de una clase no se indica etiqueta


alguna de acceso, la cualificacin por defecto es private (como en los structs
es public), reforzando as la tendencia natural en C++ hacia lo que se
denomina ocultacin de la informacin (information hiding). De la misma
manera, si en una derivacin no se indica etiqueta de acceso, tal derivacin
se entender private, reforzando de esta manera, tambin, la tendencia en
C++ a usar tal tipo de propagacin derivativa22. De cualquier forma, para
evitar innecesarias confusiones, siempre codificar las etiquetas de acceso en
las derivaciones. Veamos, sin ms dilacin, la sintaxis de derivacin:

22
Oops! De hecho la afirmacin contraria se acerca muchsimo ms a la
realidad prctica: los mecanismos de ligazn dinmica y orientacin-a-objetos de
C++ se asimilan sobremanera con la derivacin pblica. En la seccin 11.2 de ARM
se afirma, refirindose a la derivacin de clases: "Probablemente fue un error
definir un especificador de acceso por defecto.". Bien, el lector lo tiene claro: para
evitar problemas en el presente y en el futuro, codifique siempre expresamente la
cualificacin de acceso en derivacin.

C++/OOP: UN ENFOQUE PRCTICO Pgina 64/297


class Base {
public: // siguen miembros pblicos
protected: // siguen miembros protegidos
private: // siguen miembros privados
}; // clase Base ya definida

class Derivada : public Base { // derivacin pblica de Base


public: // siguen miembros pblicos de clase Derivada
protected: // siguen miembros protegidos de clase Derivada
private: // siguen miembros privados de clase Derivada
};

Notamos que simplemente se aaden, entre el nombre de la clase y la llave


de apertura de la definicin de la misma, dos puntos y el nombre de la clase
base antecedido por el cualificador de acceso deseado para la derivacin. El
resto de la clase Derivada es, en principio, igual al de una clase "normal".
Esta simple adicin va a sumergirnos, sin embargo, en el anlisis de distintas
consideraciones hasta ahora sin sentido. Pensemos, por ejemplo, que, de
alguna manera, se est aadiendo una porcin de la clase Base a la clase
Derivada. As, si definimos un constructor para la clase Derivada, qu
tendremos que hacer para construir tambin la porcin de la clase Base?
Cual ser, lgicamente, por otra parte, el funcionamiento con respecto al
destructor? Cundo, en definitiva, deber ser usado uno u otro mecanismo
de derivacin? Cmo escoger las clases base apropiadas? Etc., etc., etc.
Bien, antes de ver el qu, el cuando o el por qu, le echaremos un vistazo al
cmo. Y dado que la sintaxis es manifiestamente sencilla, ste se limitar a
la explicacin de las tres clases posibles de derivacin.

DERIVACIN PBLICA: LA FACILIDAD DE LA HERENCIA

No olvidemos que lo que se consigue con la derivacin es, en esencia, aadir


una parte de una clase a otra. Tendra, as, poco o ningn sentido la
aplicacin de una clase base slo a una clase derivada, pues en este caso
quiz lo ms prctico hubiera sido aadir simplemente la implementacin de
la clase base como cdigo a la clase derivada. Se supone, pues, que una
clase base lo ser de varias derivadas (o, al menos, tendr posibilidad
efectiva de serlo). O sea, de alguna manera en las clases base
encapsularamos las caractersticas comunes susceptibles de ser usadas por
las clases derivadas23. Pero, bueno, vemoslo en un ejemplo (suponiendo el
planeta habitado por un muy lineal tipo de animal):

23
De hecho, en la programacin efectiva con C++, lo ms frecuente es que,
habiendo reunido ms de una clase con caractersticas comunes, se haga
abstraccin de stas y se cree una superclase o clase base de las mismas y que
se insertara con naturalidad en la posible jerarqua de clases ya existente.
Naturalmente un diseo adecuado identificara apropiadamente las relaciones
iniciales de derivacin.

C++/OOP: UN ENFOQUE PRCTICO Pgina 65/297


class Persona {
public:
char* nombre() const {
return nombre_;
}
protected:
Persona() {
nombre_ = new char[ 1 ];
nombre_ = '\0';
cout << "Constructor por defecto de Persona"
<< '\n';
}
Persona( char* unNombre ) {
nombre_ = new char[ strlen( unNombre ) + 1 ];
strcpy( nombre_, unNombre );
cout << "Constructor con un argumento de Persona"
<< '\n';
}
~Persona() {
delete [] nombre_;
cout << "Destructor de Persona" << '\n';
}
private:
char* nombre_;
};

class Profesional : public Persona {


public:
void imprimeDatos() const {
cout << "Nombre: \t" << nombre() << '\n';
cout << "Aos ejercicio: \t"
<< anosEjercicio << '\n';
}
private:
int anosEjercicio;
};

Qu novedades podemos apreciar? Y cules no podemos? Creo que


merece la pena dedicar algunas lneas a estas cuitas, aunque algunas no
tengan directamente que ver con la herencia. Bueno, examinemos en primer
lugar la clase Persona. Hemos declarado dos constructores como protegidos:
esto quiere decir que sern considerados private para la clase Persona. De
esta manera el siguiente cdigo

Persona miJefe( "Mara Cristina" ); // error

sera calificado como error: al no poder acceder un objeto a los miembros


privados de su clase, no podemos construir objetos de tipo Persona! Pero
no hay que ser dramticos: no podemos construir los objetos "desde el
exterior", pero s dentro del mbito de las clases derivadas. Y cmo ...?
Enseguida vamos a ello. Recabemos antes en el detalle de los constructores:
en el cuerpo del constructor con un argumento, un puntero a char, primero

C++/OOP: UN ENFOQUE PRCTICO Pgina 66/297


reservamos espacio de la memoria de almacenamiento libre (la longitud de
la cadena del argumento ms el espacio del bit nulo final) y asignamos su
direccin al puntero nombre_, para despus copiar nuestra cadena en tal
espacio. Y no sera ms facil -podra pensar algn inquieto lector-
simplemente asignar la direccin de la cadena del argumento al miembro
nombre_? No sera ms fcil: slo ms corto y mucho ms peligroso.
Imaginemos la siguiente situacin:

class Persona {
protected:
Persona( char* unNombre ) {
nombre_ = unNombre; // peligro!!
}
// ...
};
char* maestro = "Ferrer Guardia";
void Profesional::algunaFuncion()
{
Persona miIdolo( maestro );
// ...
cout << miIdolo.nombre(); // imprime 'Ferrer Guardia'
// con la siguiente lnea vulneramos
// nuestro cuidado control de acceso
*maestro = "Friedrich Nietzsche";
cout << miIdolo.nombre(); // imprime 'Friedrich Nietzsche'
}

Realmente esto no funciona, as que descubrimos como buena la primera


codificacin. Ntese tambin, incidentalmente, que no se ha sealado como
errnea la aplicacin del constructor de la clase Persona en el mbito de la
clase Profesional, tal y como ya se haba anunciado y se explicar despus.
En cuanto al constructor por defecto, por qu no se ha codificado con el
cuerpo vaco, lo que parece suficientemente apropiado? Por algo que se
conoce como coherencia o completitud del interfaz: como esperamos que
todas las Personas posean un nombre, usaremos la funcin nombre() sin
especiales cuidados, esperando que devuelva la informacin adecuada. Qu
ocurrira, sin embargo, si el cuerpo de nuestro constructor por defecto est
vaco? Pues que el resultado de la llamada a la funcin nombre() de un
objeto creado mediante el constructor por defecto sera indefinido.
Codificando como lo hemos hecho garantizamos la seguridad de uso de tal
funcin. Naturalmente estamos obviando aqu, como se indic en captulos
anteriores, que al usar de la memoria de almacenamiento libre, deberamos
proveer a la clase de sus propias versiones del constructor de copia y del
operador de asignacin, pero esta es una cuestin que relegaremos a la
construccin de nuestra clase String. Haremos, pues, abstraccin de estas
carencias a efectos de comentar con ms simplicidad el fenmeno de la
herencia. Una ltima cosa: el lector se preguntar el sentido de las
sentencias de impresin en los constructores y destructor. Bueno, esto nos
va a ayudar a identificar cundo entra en accin cada uno de ellos, pues

C++/OOP: UN ENFOQUE PRCTICO Pgina 67/297


existen ciertos hilos conductores a veces en absoluto evidentes para el lector
no experimentado.

Examinemos ahora, levemente, la clase Profesional. Habremos de notar, al


menos, dos cosas: la clase carece de constructor (explcito), y en su nica
funcin miembro se hace referencia a lo que parecen dos miembros,
anosEjercicio (observen aqu la cruel y, por otro lado, lgica prevalencia en
informtica de una lengua fornea) y nombre(). Pero, nombre() no aparece
en la definicin de la clase Profesional! Ciertamente! Es una funcin
miembro heredada de la clase Persona, a la que s efectivamente pertenece.
Vamos a ver, por fin, cmo funciona el mecanismo derivativo.

Aunque la imagen grfica de la derivacin de clases (la adicin de la porcin


de cdigo de la clase base a la clase derivada) es acertada, quiz sea tambin
prctico, a efectos de mbitos y prevalencias, pensar en la derivacin como
en una autorizacin para que, a travs del filtro de las clases sucesivamente
derivadas, se pueda acceder a parte de la clase base. As, aunque se traspase
a la clase derivada una porcin correspondiente a la totalidad de la clase
base, la parte private de la clase base no ser accesible en ningn caso. Por
la derivacin nicamente se traspasa, pues, la posibilidad de acceso a las
secciones no privadas de la clase base: o sea, las pblicas y las protegidas.
Cul es, entonces, el nivel de acceso a estas secciones adicionales? Depende
del tipo de derivacin. Pensemos en el cualificador de derivacin como en un
cristal que tamiza el acceso, desde la transparencia a la negritud, desde las
clases derivadas y sus objetos a las secciones de la clase base. La derivacin
pblica es el cristal transparente que mantiene el nivel de acceso ya
establecido para los miembros no privados de la clase base. En nuestro caso
podemos formarnos la siguiente representacin intuitiva: a nuestra clase
Profesional simplemente se le han aadido las secciones protected y public
(naturalmente tambin se aade fsicamente la seccin private, aunque en
ningn caso se permite el acceso a la misma) de la clase Persona24. De esta
manera, como ya sabemos, desde el protocolo de descripcin de la clase
Profesional podremos acceder a los miembros de ambas secciones de la
clase base; desde los objetos de tipo Profesional podremos acceder
nicamente a la seccin pblica de la clase base (obviamos aqu,
naturalmente, el acceso a las secciones de la propia clase derivada).

Siguiendo con el esquema ldico propuesto, el lector podra imaginarse la


derivacin protected como un cristal gris, del mismo gris que tendran las
secciones protegidas de las clases si a stas se las dotara de color, que
tamizar las secciones public y protected de la clase base y convertir el

24
En realidad esto es una mera regla mnemotcnica que el lector no debe
tomar al pie de la letra, pues aparte del nivel de acceso habra que considerar las
cuestiones de prevalencia y solapamiento de miembros (pensemos, por ejemplo,
en un miembro de una clase base y otro de una clase derivada de sta que
compartan el mismo identificador: problema!).

C++/OOP: UN ENFOQUE PRCTICO Pgina 68/297


acceso para ambas a travs de la clase derivada en protected!. El lector ya
podr adivinar que la derivacin private, cual cristal oscuro, originar que, a
efectos del acceso, la clase derivada considerar como private las secciones
public y protected de la clase base. Pero esto lo veremos un poco ms
adelante: volvamos a la derivacin pblica.

Segn lo expuesto, ahora podemos comprender que desde el interior de la


clase Profesional tenemos acceso a la funcin miembro nombre(), como si
sta, a los solos efectos del nivel de acceso, hubiera sido directamente
declarada como pblica en la clase derivada. Hubiera sido un error, sin
embargo, codificar lo siguiente:

class Profesional : public Persona {


public:
void imprimeDatos() const {
cout << "Nombre: \t" << nombre_ << '\n'; // error
cout << "Aos ejercicio: \t"
<< anosEjercicio << '\n';
}
// sigue definicin de clase
};

pues el dato miembro nombre_ no pertenece a una seccin con acceso


private desde la clase Profesional, sino que pertenece a la seccin private de
la clase base (que no es lo mismo!), y esta seccin es, en cualquier caso y
tipo de derivacin, inaccesible desde las clases derivadas (la porcin est ah,
fsicamente, pero no puede ser accedida si no es a travs de las secciones
pblica y protegida de su propia clase).

CONSTRUCTORES DE CLASES EN JERARQUAS DE DERIVACIN

Bien, traspasemos ahora nuestra atencin al siempre espinoso tema de los


constructores. Notamos que la clase Profesional carece de constructor
explcito, por lo que, como sabemos, el sistema la proveer con uno
implcito. De acuerdo: pero, cmo se construye la porcin de la clase
base?. Examinemos el siguiente simplsimo ejemplo:

int main( int, char** )


{
Profesional sexadorDePollos;
return 0;
}

Puede el lector adivinar qu ocurre cuando se ejecuta este cdigo? Veamos


la salida:

Constructor por defecto de Persona


Destructor de Persona

C++/OOP: UN ENFOQUE PRCTICO Pgina 69/297


Por qu? La informacin para construir una parte de nuestro objeto reside
en la clase base Persona, por lo que antes de aplicar el constructor implcito
por defecto para la clase derivada, el sistema exige que se construya la
porcin de la clase base, y para esto busca en sta un constructor por
defecto (que en nuestro caso s existe) y lo ejecuta, para aplicar despus la
construccin de la porcin correspondiente a la clase derivada.
Recursivamente, si nuestra clase base fuera, a su vez, derivada de otra, antes
de usar el constructor de sta se forzara la construccin de la porcin de su
clase base, etc. Pero quiz estamos en un estadio demasiado elemental para
entrar en detalles. Dotemos, pues, a nuestra clase Profesional de
constructores y destructor expresos:

class Profesional : public Persona {


public:
Profesional();
Profesional( int );
~Profesional() {
cout << "Destructor de Profesional" << '\n';
}
// sigue resto definicin de clase
};

Profesional::Profesional() : anosEjercicio( 0 )
{
cout << "Constructor por defecto de Profesional" << '\n';
}

Profesional::Profesional( int experiencia )


: anosEjercicio( experiencia )
{
cout << "Constructor con un argumento de Profesional"
<< '\n';
}

Si con las adiciones de cdigo expuestas ejecutamos de nuevo el anterior


programita, la salida cambiar de esta forma:

Constructor por defecto de Persona


Constructor por defecto de Profesional
Destructor de Profesional
Destructor de Persona

Ahora podemos apreciar mejor una cuestin de orden: primero se construye


la porcin del objeto correspondiente a sus clases base (lo cual es lgico,
pues pensemos que en el constructor de la clase derivada se puede hacer
referencia a miembros de sus clases base, y si estos an no han sido
construidos entonces: problema!); los destructores, sin embargo, siguen un
orden inverso: primero se destruye la porcin del objeto correspondiente a
la clase ms derivada y despus, sucesivamente, las porciones de las clases
base en jerarqua. Si pensamos un poco en este orden inamovible,
apercibiremos una fcil consecuencia: en el cuerpo de un constructor de una

C++/OOP: UN ENFOQUE PRCTICO Pgina 70/297


clase base no se puede hacer referencia a un miembro (y, por supuesto, a
un constructor) de una clase derivada, pues sta, cuando se est ejecutando
el cuerpo de tal constructor de la clase base, todava no ha sido construida!.

INICIALIZACIN DE CLASES BASE

De acuerdo, de acuerdo: las nubes desaparecen, poco a poco, del horizonte


de la derivacin, pero lo cierto es que todava no hemos solucionado un
problema: en cualquier caso construimos una porcin de clase base en
nuestros objetos, pero sta contiene, siempre, un valor por defecto. En
realidad nosotros deberamos querer construir un Profesional como una
Persona, con su propio nombre, con determinados aos de experiencia.
Pero, cmo podemos inicializar el miembro nombre_, si desde la clase
derivada no tenemos acceso al mismo? Fcilmente: cmo se accede a los
miembros "escondidos" de una clase? A travs de las funciones miembro
que conforman el interfaz de la clase! Y a travs de qu funcin miembro
podramos, en nuestro caso, acceder a nombre_? Pues a travs de un
constructor de la clase base, naturalmente, que es la nica funcin miembro
con la que contamos. Y no sera mejor dotar a la clase base de una funcin
de acceso public que permitiera la modificacin del dato miembro nombre_,
como por ejemplo

Persona::modificaNombre( char* unNombre )


{
delete nombre_;
nombre_ = new char[ strlen( unNombre ) + 1 ];
strcpy( nombre_, unNombre );
cout << "Funcin miembro modificaNombre" << '\n';
}

? Pues realmente no: montemos una pequea simulacin, aadiendo un


nuevo constructor a nuestra clase derivada:

class Profesional : public Persona {


public:
Profesional( char*, int );
// sigue resto definicin de clase
};

Profesional::Profesional( char* unNombre, int experiencia )


: anosEjercicio( experiencia )
{
modificaNombre( unNombre );
cout << "Constructor con dos argumentos de Persona"
<< '\n';
}

Solucionado todo? Ms bien no! Veamos la salida que arroja, con esta
nueva adicin, la siguiente variacin de nuestro simptico programita:

int main( int, char** )

C++/OOP: UN ENFOQUE PRCTICO Pgina 71/297


{
Profesional sexadorDePollos( "Mario", 20 );
return 0;
}

He aqu el resultado:

Constructor por defecto de Persona


Constructor con dos argumentos de Profesional
Funcin miembro modificaNombre
Destructor de Profesional
Destructor de Persona

Vemos que nuestra implementacin no es muy eficiente: la porcin de clase


base se sigue construyendo con el constructor por defecto de Persona, y ms
tarde se usa una funcin miembro para cambiar el nombre. No podramos
trocar estos dos procesos en uno solo? Por supuesto, usando, como ya se
apunt, el constructor con un argumento de la clase base. Pero, cmo?
cmo podemos aplicar tal constructor exactamente al objeto derivado que
estamos construyendo? Pues, siguiendo un razonamiento similar al
observado respecto de la inicializacin de miembros en constructores,
mediante su inclusin expresa en la lista de inicializacin del constructor de
la clase derivada. Desechemos, pues, la funcin modificaNombre(...) e
intentemos lo apuntado:

Profesional::Profesional( char* unNombre, int experiencia )


: Persona( unNombre ), anosEjercicio( experiencia )
{
cout << "Constructor con dos argumentos de Profesional"
<< '\n';
}

La salida del ltimo programa quedara ahora de la siguiente guisa:

Constructor con un argumento de Persona


Constructor con dos argumentos de Profesional
Destructor de Profesional
Destructor de Persona

Con esta sintaxis lo que hacemos es indicar de forma expresa al constructor


de nuestro objeto qu constructor en concreto de la clase base debe utilizar.
Naturalmente slo podemos hacer esto con respecto a la clase base de la
nuestra, pero sin poder irnos ms lejos en la escala derivativa. Intentemos
ahora, para aclarar definitivamente las cuestiones de orden, una derivacin
adicional:

class Directivo : public Profesional {


public:
Directivo( long unSalario = 0) : salario( unSalario ) {
cout << "Constructor por defecto de Directivo"
<< '\n';

C++/OOP: UN ENFOQUE PRCTICO Pgina 72/297


}
Directivo( char* unNombre, int experiencia,
long unSalario)
: Profesional( unNombre, experiencia ),
salario( unSalario ) {
cout << "Constructor con tres argumentos de Directivo"
<< '\n';
}
~Directivo() {
cout << "Destructor de Directivo" << '\n';
}
private:
long salario;
};

Como vemos, en la lista de inicializacin del constructor con tres argumentos


de Directivo aparece el constructor de su clase base y la inicializacin de su
nico dato miembro. Si hubiramos intentando forzar el uso de un
determinado constructor de la clase Persona, el compilador lo sealara
como error indicando que Persona no es una clase base de Directivo.
Veamos qu ocurre ahora con nuestro programilla, convenientemente
modificado:

int main( int, char** )


{
Profesional gigolo( "Maurice", 7 );
Directivo directorComercial( "Ramn", 20, 50000 );
return 0;
}

He aqu el resultado (los comentarios son mos, a efectos de clarificacin):

// construccin del objeto Profesional


Constructor con un argumento de Persona
Constructor con dos argumentos de Profesional
// construccin del objeto Directivo
Constructor con un argumento de Persona
Constructor con dos argumentos de Profesional
Constructor con tres argumentos de Directivo
// seguidamente se destruyen ambos objetos,
// pero el orden de destruccin no tiene
// por qu ser el que aqu se va a indicar
// por ejemplo: primero se puede destruir el objeto Directivo
// tal y como ocurrira con el compilador Borland C++
Destructor de Directivo
Destructor de Profesional
Destructor de Persona
// ahora se destruira (en nuestro supuesto)
// el objeto Profesional
Destructor de Profesional
Destructor de Persona

C++/OOP: UN ENFOQUE PRCTICO Pgina 73/297


El esquema de orden de intervencin de constructores y destructores, aun
ampliado con una nueva clase, sigue siendo el mismo. Cabe comentar aqu,
pues viene a colacin, lo apuntado en las lneas anteriores sobre el orden en
que se destruirn los objetos (que no tiene que ver con el orden en que se
aplicarn los destructores en una clase derivada de otras, como ya
sabemos). Bien, C++ nos depara otra sorpresa? Lo cierto es que esto no
tiene que ver directamente con la herencia, pero no est de ms anotarlo:
cuando entra en accin el destructor de un objeto? En cualquier momento
desde el ltimo uso del objeto en el cdigo hasta el final del mbito del
mismo: no hay momento prefijado ni prevalencia de objetos, por lo que el
lenguaje no asegura que, en el caso anterior, el objeto Directivo deba
destruirse antes que el objeto Persona. Realmente esta cuestin depende
nicamente del compilador.

DERIVACIONES NO-PBLICAS: SLO PARA TUS OJOS

Como ya hemos apuntado, la derivacin pblica ocasiona que las secciones


no-privadas de la clase base puedan ser abordadas con su mismo nivel de
acceso, pero con respecto a la clase derivada. O sea, las secciones public y
protected de la clase base tendrn cualificacin de acceso public y protected,
recpectivamente, desde la clase derivada (recordemos el smil del cristal
transparente, que mantiene invariantes los niveles de acceso).

La derivacin puede ser, tambin, protected (lisez cristal gris), ocasionando


que las secciones no-privadas de la clase base se conviertan en secciones
protegidas con respecto de la clase derivada. O sea, sern privadas para la
clase derivada, aunque podrn ser accedidas, al menos, por las clases (no
por los objetos) derivadas de sta. As, los miembros de las secciones public
y protected de la clase base se convierten en miembros protected de la clase
derivada.

La derivacin podra tambin cualificarse, al fin, como private (lisez cristal


negro), siendo as que las secciones no-privadas de la clase base se
convertirn en secciones private con respecto de la clase derivada. Esto es,
sern privadas para la clase derivada, e inaccesibles para las clases derivadas
de sta. De esta forma, los miembros de las secciones public y protected de
la clase base se convertirn en miembros private de la clase derivada.

Queda claro, pues, que la derivacin pblica es la nica que permite, en


todo caso, a los objetos de clases derivadas acceder a determinados
miembros de sus clases base (los insertados en la seccin public de stas).
Intuitivamente podemos apreciar, tambin, que las derivaciones no-pblicas
sirven, sobre todo, para la reutilizacin "interna" del cdigo ya escrito en
clases bases.

Bien, vayamos a otra cuestin. Aun ceindonos slo a la herencia simple,


imaginemos el siguiente panorama: de una clase A se deriva pblicamente

C++/OOP: UN ENFOQUE PRCTICO Pgina 74/297


otra B, y de sta se deriva otra, C, con cualificacin protegida; y de sta, a
su vez, se deriva privadamente otra D; y de sta, de nuevo, se deriva como
protegida una nueva clase E, y as ad nauseam. Y si ahora preguntamos: a
qu accede D, a qu no puede acceder E, a qu ...? Despacio, despacio. Lo
cierto es que si nos encontramos en la realidad con una algaraba semejante,
lo primero que debemos hacer es inquirir de qu sanatorio mental escap el
autor del desaguisado, para devolverlo all con rapidez, por la fuerza si es
necesario. Quiere decir esto que no podemos mezclar distintos tipos de
derivacin en una jerarqua de clases? Ni mucho menos! Son frecuentes
derivaciones del tipo pblico-pblico-pblico-privado privado-pblico--
pblico-pblico, pero, como es fcilmente apreciable, estos esquemas
ofrecen, en comparacin, una gran limpieza. Volvemos, pues, a una muy
repetida apreciacin sobre C++: el lenguaje proporciona muchos
instrumentos que refuerzan su gran flexibilidad, pero a la vez insta a los
usuarios a utilizarlos con mucha prudencia. Por otro lado es momento de
anunciar algo que al lector le puede parecer, cuando menos, sorprendente:
si se usa la derivacin de clases como un mero mecanismo formal, ms o
menos ingenioso, de ahorrar trabajo al desarrollador, se estar incurriendo
en el peor error de todos, como si usramos un martillo piln para partir la
cscara de un huevo, con la desventaja adicional del coste elctrico que
supone cada golpe. An ms: la siguiente consecuencia de esta actitud ser,
probablemente, que el mismo desarrollador abandone el uso de los
mecanismos de derivacin en su cdigo, cansado de sus pocas ventajas y
muchas dificultades. Se sorprenden? Pues sepan que, en la actualidad, la
mayora de los usuarios de C++ lo utilizan como un "mejor C": esto quiere
decir que slo una pequea parte de los mismos utilizan las clases, y de
stos nicamente una pequesima porcin usa de los mecanismos de
derivacin (y esto no son imaginaciones mas, sino que se ha puesto de
manifiesto en los ltimos congresos, seminarios y jornadas sobre C++
celebrados en USA)25. Teniendo en cuenta que la OOP en C++ se sostiene,
sobre todo, por la derivacin de clases y las funciones virtuales (ligadas a
aqulla), resulta as que "mucho C++ y poco OOP". Entonces, es necesario
meditar durante varios aos en un almacn de cereales para poder usar con
juicio los mecanismos de derivacin en C++? No, no me han entendido. No
es que sea particularmente difcil comprender tales mecanismos. Lo
verdaderamente difcil es comprender el tipo de relaciones del mundo real
que pueden ser capturados con los mismos. Si uno llega a comprender el
sustrato semntico que anima las distintas formas de derivar clases, a la vez
que se compromete con el buen diseo de las clases individuales y de sus
interfaces, buena parte del trabajo ya estar hecho.

25
Dada la apreciable segmentacin en el uso del lenguaje, se han llegado a
establecer tres diferentes niveles de uso de C++: "mejor C" o "C con chequeo de
tipos", que utiliza todas las caractersticas de C++ no relacionadas con las clases;
"C++ basado en Objetos", que usa de las clases, aunque no de la derivacin; y,
por ltimo, "C++ completo", que abarca la totalidad del lenguaje.

C++/OOP: UN ENFOQUE PRCTICO Pgina 75/297


Volviendo a lo anterior (a las jerarquas con derivaciones mixtas), el lector
deber observar la siguiente norma secuencial: primero aplicar la primera
derivacin y retendr el nuevo nivel de acceso de las secciones; sobre estos
nuevos niveles de acceso aplicar la segunda derivacin, y as
sucesivamente. En la prctica no es difcil, y slo requiere cuidado.

Seguidamente podremos ver, no s si para bien o para mal, un esquema de


cdigo en el que se explicitan diversos niveles de acceso en derivacin,
aunque sin agotar todas las posibilidades (que necesitaran de un enorme
nmero de pginas y que resultaran de todava ms dudosa utilidad para el
sufrido lector):

class Base {
public:
void pruebaAcceso();
void pubBf() {}
protected:
void proBf() {}
private:
void priBf() {}
};

class PublicDerived : public Base {


friend class FriendPublicDerived;
public:
void pruebaAcceso();
void pubPUBDf() {}
protected:
void proPUBDf() {}
private:
void priPUBDf() {}
};

class ProtectedDerived : protected Base {


friend class FriendProtectedDerived;
public:
void pruebaAcceso();
void pubPRODf() {}
protected:
void proPRODf() {}
private:
void priPRODf() {}
};

class PrivateDerived : private Base {


friend class FriendPrivateDerived;
public:
void pruebaAcceso();
void pubPRIDf() {}
protected:
void proPRIDf() {}
private:
void priPRIDf() {}

C++/OOP: UN ENFOQUE PRCTICO Pgina 76/297


};

class FriendPublicDerived {
public:
void pruebaAcceso();
PublicDerived objetoPublicDerived;
};

class FriendProtectedDerived {
public:
void pruebaAcceso();
ProtectedDerived objetoProtectedDerived;
};

class FriendPrivateDerived {
public:
void pruebaAcceso();
PrivateDerived objetoPrivateDerived;
};

void Base::pruebaAcceso()
{
pubBf();
proBf();
priBf();
}

void PublicDerived::pruebaAcceso()
{
pubBf();
proBf();
priBf(); // error
}

void ProtectedDerived::pruebaAcceso()
{
pubBf();
proBf();
priBf(); // error
}

void PrivateDerived::pruebaAcceso()
{
pubBf();
proBf();
priBf(); // error
}

void FriendPublicDerived::pruebaAcceso()
{
PublicDerived objetoPublicDerived;
objetoPublicDerived.pubBf();
objetoPublicDerived.proBf();
objetoPublicDerived.priBf(); // error

C++/OOP: UN ENFOQUE PRCTICO Pgina 77/297


objetoPublicDerived.pubPUBDf();
objetoPublicDerived.proPUBDf();
objetoPublicDerived.priPUBDf();
}

void FriendProtectedDerived::pruebaAcceso()
{
ProtectedDerived objetoProtectedDerived;
objetoProtectedDerived.pubBf();
objetoProtectedDerived.proBf();
objetoProtectedDerived.priBf(); // error
objetoProtectedDerived.pubPRODf();
objetoProtectedDerived.proPRODf();
objetoProtectedDerived.priPRODf();
}

void FriendPrivateDerived::pruebaAcceso()
{
PrivateDerived objetoPrivateDerived;
objetoPrivateDerived.pubBf();
objetoPrivateDerived.proBf();
objetoPrivateDerived.priBf(); // error
objetoPrivateDerived.pubPRIDf();
objetoPrivateDerived.proPRIDf();
objetoPrivateDerived.priPRIDf();
}

int main( int, char** )


{
Base objetoBase;
objetoBase.pubBf();
objetoBase.proBf(); // error
objetoBase.priBf(); // error

PublicDerived objetoPublicDerived;
objetoPublicDerived.pubBf();
objetoPublicDerived.proBf(); // error
objetoPublicDerived.priBf(); // error

ProtectedDerived objetoProtectedDerived;
objetoProtectedDerived.pubBf(); // error
objetoProtectedDerived.proBf(); // error
objetoProtectedDerived.priBf(); // error

PrivateDerived objetoPrivateDerived;
objetoPrivateDerived.pubBf(); // error
objetoPrivateDerived.proBf(); // error
objetoPrivateDerived.priBf(); // error

FriendPublicDerived objetoFriendPublicDerived;
objetoFriendPublicDerived.
objetoPublicDerived.pubBf();
objetoFriendPublicDerived.

C++/OOP: UN ENFOQUE PRCTICO Pgina 78/297


objetoPublicDerived.proBf(); // error
objetoFriendPublicDerived.
objetoPublicDerived.priBf(); // error
objetoFriendPublicDerived.
objetoPublicDerived.pubPUBDf();
objetoFriendPublicDerived.
objetoPublicDerived.proPUBDf(); // error
objetoFriendPublicDerived.
objetoPublicDerived.priPUBDf(); // error

FriendProtectedDerived objetoFriendProtectedDerived;
objetoFriendProtectedDerived.
objetoProtectedDerived.pubBf(); // error
objetoFriendProtectedDerived.
objetoProtectedDerived.proBf(); // error
objetoFriendProtectedDerived.
objetoProtectedDerived.priBf(); // error
objetoFriendProtectedDerived.
objetoProtectedDerived.pubPRODf();
objetoFriendProtectedDerived.
objetoProtectedDerived.proPRODf(); // error
objetoFriendProtectedDerived.
objetoProtectedDerived.priPRODf(); // error

FriendPrivateDerived objetoFriendPrivateDerived;
objetoFriendPrivateDerived.
objetoPrivateDerived.pubBf(); // error
objetoFriendPrivateDerived.
objetoPrivateDerived.proBf(); // error
objetoFriendPrivateDerived.
objetoPrivateDerived.priBf(); // error
objetoFriendPrivateDerived.
objetoPrivateDerived.pubPRIDf();
objetoFriendPrivateDerived.
objetoPrivateDerived.proPRIDf(); // error
objetoFriendPrivateDerived.
objetoPrivateDerived.priPRIDf(); // error

return 0;
}

CONVERSIONES ESTNDAR BAJO DERIVACIN

Bien, volvamos a la representacin fsica de la herencia y apliqumosla a un


objeto del tipo Directivo: tal objeto estara compuesto de tres partes,
correspondientes a las tres clases que con el mismo tienen que ver: la clase
Directivo, a que directamente pertenece, y las clases Profesional, de la que
aqulla deriva, y Persona, de la que Profesional deriva a su vez. Tan exacta
es esta particin fsica del objeto en porciones que realmente se puede
acceder al mismo ... a travs de tres punteros distintos! As, por ejemplo, se
cumplira que:

C++/OOP: UN ENFOQUE PRCTICO Pgina 79/297


Directivo* punteroADirectivo = new Directivo( "Emilio",
2, 40000 );
Profesional* punteroAPorcionProfesionalDeDirectivo
= (Profesional*) punteroADirectivo;
Persona* punteroAPorcionPersonaDeDirectivo
= (Persona*) punteroADirectivo;

punteroAProcionPersonaDeDirectivo->imprimeDatos(); // OK
punteroADirectivo->imprimeDatos(); // OK
punteroAPorcionProfesionalDeDirectivo->imprimeDatos(); // OK

De hecho, esta codificacin viene a expresar algo as como: "consideremos el


objeto de tipo Directivo desde distintos puntos de vista". Demonios! Quiere
esto decir que podemos esperar resultados diferentes dependiendo de la
parte de un objeto a travs de la que accedemos al mismo? Efectivamente!
Aunque sera mejor exponerlo de la siguiente forma: tenemos la posibilidad
fsica de cambiar el comportamiento de un objeto dependiendo de la
cualificacin del puntero o referencia desde el que lo accedamos, pero
tenemos tambin la obligacin (dentro de la tica del lenguaje) de no usar
(como norma) esta caracterstica. Ya tendremos ocasin de discutir ste y
otros puntos cuando examinemos la semntica que se oculta tras el
mecanismo formal de derivacin.

Vemos en el cdigo, tambin, que se han realizado sendos casts (todava no


sabemos nada de conversiones implcitas) sealando expresamente la
conversin ... de qu? del tipo del objeto apuntado? No, por supuesto!
nicamente del puntero que apunta al objeto, de tipo invariante, para que
pueda producirse la asignacin. Esta conversin expresa (de un puntero o
referencia de una clase derivada a un puntero o referencia de una de sus
clases base, directas o indirectas) es siempre segura: las clases derivadas
siempre poseen una porcin de sus clases base. La conversin inversa no es
segura: no se puede asegurar que una clase base contenga (arriba o abajo,
pues el posicionamiento depende de la implementacin) una porcin de
clase derivada (todos los Profesionales son Personas, pero no todas las
Personas son Profesionales), de tal manera que esa codifificacin supone un
metaconocimiento de la estructura del programa, propio del desarrollador,
favorecedor de errores y dependencias sin evidencia documental para el
cliente de las clases.

Vaya! Entonces, y con todo, es as de simple? -preguntara aqu el lector-


Qu pasa, pues, con los accesos bajo derivacin? No influyen aqu?
Naturalmente que s! El lector, como siempre, ha puesto el dedo en la llaga.
Recordemos que la derivacin pblica, aplicable a las clases notadas en el
cdigo, no impone en las clases derivadas ninguna restriccin de acceso
sobre los miembros de las clases base. Podramos decir, as, que las
porciones del objeto correspondientes a las clases bases pblicas son
"pblicas" para ste, entendiendo aqu el termino "pblico" en un sentido
extensivo ms que como mera etiqueta de acceso, exactamente igual que si
dijramos que la totalidad de los miembros de una clase son "pblicos" con

C++/OOP: UN ENFOQUE PRCTICO Pgina 80/297


respecto de una funcin miembro o amiga. De hecho, vamos a considerar,
antes de seguir, un nuevo punto de vista, ms general, sobre los niveles de
acceso en jerarquas interrelacionadas de clases. Dejemos a un lado las
etiquetas de acceso incluidas en la definicin del lenguaje e, intentando que
el lector olvide lo hasta ahora aprendido, califiquemos el acceso desde un
mbito a una funcin o variable cualquiera, a la brava, simplemente de la
siguiente forma (y aqu las maysculas aportarn un nivel de distincin
importante para el lector): PBLICO si se puede acceder, PRIVADO si no se
puede. Bajo esta nueva luz, reconsideremos ahora lo ya visto:

- La seccin private de una clase es PBLICA con respecto de (en el


mbito de) funciones miembros de la clase y funciones amigas de la
clase, y es PRIVADA para el resto del programa.
- La seccin protected de una clase es PBLICA con respecto de las
funciones miembros y amigas de la propia clase (como la seccin
private) y tambin de las funciones miembros y amigas de las clases
derivadas de sta (he aqu la diferencia con la seccin anterior),
mientras que es PRIVADA para el resto del programa.
- La seccin public de una clase es PBLICA para todo el programa.

Y ahora ya estamos preparados para enunciar una regla fundamental:


cuando en un determinado mbito se da una relacin de derivacin con
respecto a una clase base dada, con cualificacin de acceso PBLICA,
entonces son de aplicacin las siguientes conversiones estndar:

- un puntero a la clase derivada se convierte implcitamente en un


puntero a la clase base de acceso PBLICO en tal mbito.
- una referencia a la clase derivada se convierte implcitamente en una
referencia a la clase base de acceso PBLICO en tal mbito.
- un objeto de la clase derivada se convierte implcitamente en un
objeto de la clase base de acceso PBLICO en tal mbito.
- un puntero a un miembro de la clase base, de acceso PBLICO en
tal mbito, se convierte implcitamente en un puntero a un miembro
de la clase derivada.

Por supuesto una derivacin public equivale a un acceso PBLICO en


cualquier mbito. Alto! Un momento! No conserva la derivacin pblica los
niveles de acceso de la clase base? No habr, as, secciones en una
derivacin pblica inaccesibles para los objetos? Naturalmente! Al decir que
una derivacin public supone un acceso PBLICO estoy nicamente
afirmando que tal tipo de derivacin posibilita el acceso, en todo mbito, a
la porcn de la clase base. Naturalmente, tambin, si tal clase base no
dispusiera, por ejemplo, de seccin public, los objetos de las clases
derivadas de la misma (y tambin los objetos de la misma clase base) no
podran acceder directamente a ningn miembro de esa clase base. Veamos
otro ejemplo exactamente a la inversa: si, verbigratia, la clase base no
tuviera secciones private o protected, el acceso desde la clase derivada no
observara ninguna restriccin real. La verdad es que usar los mismos

C++/OOP: UN ENFOQUE PRCTICO Pgina 81/297


trminos para designar distintos conceptos no es, ciertamente, algo que
ayude a los principiantes en el aprendizaje del lenguaje. En realidad, donde
aparece PBLICO debera decir "DE ACCESO POSIBLE", y donde aparece
PRIVATE debera aparecer "DE ACCESO IMPOSIBLE". Pero lo cierto es que
en todos los textos (ingleses, claro) sobre C++ se emplea profusamente la
ambigua notacin expuesta (sin distinguir entre maysuculas y minsculas),
esperando de la inteligencia del lector el instantneo reconocimiento del
contexto que cualifica uno u otro significado. As las cosas y para no
acostumbrar al lector a facilidades que en ningn otro texto va a encontrar,
en adelante hablaremos de accesos pblicos o privados, sin ms.

Bien: decamos que, en todo mbito, los punteros y referencias a objetos de


una clase derivada se convierten implcitamente en punteros y referencias,
respectivamente, a objetos de la clase de la que pblicamente se derivan.
Los objetos de la clase derivada se convierten implcitamente, tambin, en
objetos de la clase de la que pblicamente se deriva aqulla, etc. etc. De esta
forma podramos codificar:

Directivo* punteroADirectivo = new Directivo( "Emilio",


2, 40000 );
Profesional* punteroAProfesional = punteroADirectivo;
Persona& referenciaAPersona = *punteroADirectivo;
Profesional encargado( "Enrique", 20 );
Persona marido = encargado;

Note el lector que aqu no aparecen casts, pues con arreglo a lo expuesto las
conversiones codificadas se pueden producir de forma implcita.

Pero tambin existen otros mbitos, en derivaciones no-pblicas, donde se


dan accesos PBLICOS (lisez "posibilidades de acceso") a las clases base.
Imaginemos pues, con fines didcticos, el siguiente esquema derivativo:

class Base { /* ... */ };


class DerivadaProtegida : protected Base { /* ... */ };
class DerivadaProtegidaProtegida
: protected DerivadaProtegida {
// cualquier etiqueta de acceso
void unaFuncion( DerivadaProtegida* pObjetoD,
DerivadaProtegidaProtegida* pObjetoDD )
{
Base* punteroABase = pObjetoD; // OK (Atencin:
// ver nota 26 )
punteroABase = pObjetoDD; // OK (Atencin:
// ver nota 26 )
pObjetoD = pObjetoDD; // OK: conversin implcita
// ...
}
// ...
};

C++/OOP: UN ENFOQUE PRCTICO Pgina 82/297


Qu ocurre aqu? Realmente en el mbito de definicin de unaFuncion se
tiene acceso pblico a las clase bases directa (DerivadaProtegida) e indirecta
(Base), siendo ambas clases base protected? Bueno, si el lector quiere
convencerse slo tiene que comprobar lo siguiente: se puede acceder desde
tal mbito a las secciones public y protected de las clases base?
Naturalmente que s, en este caso! S, desde una clase derivada a la clase
base directa de la que esta se deriva de forma protegida, pues recordemos
que lo que as se consigue es transformar las secciones public y protected de
la clase base en secciones protected de la clase derivada, y, por supuesto,
una funcin miembro de una clase cualquiera tiene acceso (PBLICO) a
todas sus secciones! S, tambin, con respecto de la clase base indirecta,
pues al derivar DerivadaProtegida de sta de forma protegida, las secciones
no-private de la clase Base se convierten en secciones protected de la clase
derivada, y aqu podramos enlazar el razonamiento para con la clase base
directa26.

Bueno, revisemos ahora un ejemplo un tanto ms complejo:

class Base { /* ... */ };


class DerPub : public Base { /* ... */ };
class DerProtPub : protected DerPub { /* ... */ };
class DerPrivProtPub : private DerProtPub {
// cualquier etiqueta de acceso
void otraFuncion( Base* pB, DerPub* pDP,
DerProtPub* pDPP,
DerPrivProtPub* pDPPP )
{
pB = pDP; // OK: Base es una clase base
// pblica de DerPub
pB = pDPP; // OK (Atencin: ver nota 26 )
pB = pDPPP; // OK (Atencin: ver nota 26 )
pDP = pDPP; // OK (Atencin: ver nota 26 )
pDP = pDPPP; // OK (Atencin: ver nota 26 )
pDPP = pDPPP; // OK: las funciones amigas y
// miembros de una clase derivada
// (cual es la presente)
// privadamente de otra
// tienen acceso pblico
// a la clase base
}
// ...
};

26
Entramos de nuevo en el espinoso tema de las implementaciones
particulares de C++: aun cuando lo expuesto es correcto y lgicamente impecable,
si intentamos compilar los ejemplos con, verbigratia, Borland C++ 4.0, algunas
lneas sern flageladas como errores del tipo "No se puede convertir X* a Y*".
Concretamente el compilador encontrar problemas, normalmente, con los niveles
de acceso a las clases base indirectas. Estamos, simplemente, ante una limitacin
del compilador.

C++/OOP: UN ENFOQUE PRCTICO Pgina 83/297


funcionGlobal( Base* pB, DerPub* pDP,
DerProtPub* pDPP, DerPrivProtPub* pDPPP )
{
pB = pDP; // OK: Base es una clase base
// pblica de DerPub
pB = pDPP; // Error
pB = pDPPP; // Error
pDP = pDPP; // Error
pDP = pDPPP; // Error
pDPP = pDPPP; // Error
}

Bueno, a pesar de lo expuesto, yo me dara por satisfecho si el lector


retuviera solamente la siguiente idea parcial: si una clase D deriva
pblicamente, directa o indirectamente, de otra B, dondequiera que aparezca
un objeto B o un puntero o referencia a un objeto B, se puede aplicar un
objeto D, o un puntero o referencia a un objeto D, respectivamente. De esta
manera se quiere indicar que un D es un B. Bueno, vemoslo con
antropolook: Profesional deriva pblicamente de Persona, lo que quiere
decir, informalmente, que un profesional es una pesona, y, por tanto, donde
y como quiera que acte una persona puede actuar, siempre, un profesional:
si una persona puede imprimir su edad, un profesional tambin. Lo mismo
ocurrira con la derivacin pblica de Directivo con respecto de Profesional.
Claro que el razonamiento inverso no siempre funciona: o sea, un persona
no tiene por qu ser un profesional, pues este tiene caractersticas (como
miembros que tendra la clase) que no son de aplicacin a aqulla (como los
aos de ejercicio).

De acuerdo: ya hemos visto las conversiones estndar, pero de qu puede


servir, en la prctica, todo esto? Bueno, se espera que nos habr de
simplificar la vida, permitindonos montar codificaciones "genricas" como la
siguiente:

void comoTeLlamas( const Profesional& individuo )


{
// un objeto Profesional tiene acceso a la
// seccin pblica de la clase Persona
cout << Profesional.nombre();
}

Profesional unProfesional( "Emilio", 2 );


comoTeLlamas( unProfesional ); // OK: imprime "Emilio"
Directivo unDirectivo( "Don Emilio", 2, 50000 );
// un Directivo es un Profesional,
// y en la siguiente lnea se produce
// una conversin implcita de Directivo a Profesional
comoTeLlamas( unDirectivo ); // OK: imprime "Don Emilio"

C++: UN PAS DE MARAVILLAS

C++/OOP: UN ENFOQUE PRCTICO Pgina 84/297


Si el lector no ha asimilado del todo lo hasta ahora expuesto, al menos
habr retenido un concepto: "los miembros de la seccin privada de una
clase no se pueden acceden desde fuera del protocolo de descripcin de su
propia clase (habiendo abstraccin de las funciones amigas, un mal
necesario que debemos evitar en lo posible)". La idea que predomina en la
orientacin a objetos de C++ es, por otro lado, que "cambios pequeos
originan pequeas repercusiones". Imaginemos ahora una clase insertada en
una jerarqua, con su cdigo utilizado y re-utilizado por otras clases y
objetos: los miembros de la seccin private de nuestra clase no formarn
parte del interfaz utilizado por los clientes de la misma. Si modificamos, no
obstante, tal seccin private, inmersa en un archivo de cabecera donde se ha
definido completamente la clase, al cambiar la fecha adscrita al archivo de
cabecera nuestra utilidad make forazar la recompilacin de nuestro
programa: esto no es lgico! De alguna manera tendramos que convertir la
barrera lgica de restriccin de acceso a la seccin private en una barrera
formal que impidiera estas no-deseadas consecuencias. Una buena idea a
este respecto es la aportada por John Carolan en su trabajo "Construyendo
clases a prueba de balas". Se trata, en definitiva, de la tcnica del "Gato de
Cheshire" (y aqu los lectores de Carroll recordarn las voluntarias desapari-
ciones parciales del risueo animal). Es como si dijramos: aqu est la cola
del gato, dnde est el cuerpo? La tcnica consiste, pues, en sustituir la
seccin private de una clase por un puntero a otra clase, con acceso total
private, donde se implemente el detalle de la representacin interna de la
primera y se declare ste como amiga. Un poco oscuro? Vemoslo en la
prctica: por un lado tenemos un fichero de cabecera donde aparece el
siguiente cdigo:

class Persona;

class RepInternaPersona { // el cuerpo del gato de Cheshire


friend class Persona; // la situacin de la cola
// de nuestro gato.
private:
RepInternaPersona() {
nombre_ = new char[ 1 ];
nombre = '\0';
}
RepInternaPersona( char* unNombre ) {
nombre = new char[ strlen( unNombre ) + 1 ];
strcpy( nombre_, unNombre );
}
~RepInternaPersona() {
delete [] nombre_;
}
private:
char* nombre_;
};

Por otro lado tenemos el fichero en el que definimos nuestra clase Persona:

C++/OOP: UN ENFOQUE PRCTICO Pgina 85/297


class RepInternaPersona;

class Persona {
public:
char* nombre() const {
return rip->nombre_;
}
protected:
Persona() {
rip = new RepInternaPersona;
}
Persona( char* unNombre ) {
rip = new RepInternaPersona( unNombre );
}
~Persona() {
delete rip;
}
private:
RepInternaPersona* rip; // la cola del gato de Cheshire
};

Francamente, el cdigo escrito no es demasiado eficiente (ni siquiera en el


tratamiento de los constructores), pero mirmoslo desde este punto de vista:
modificar la representacin privada de nuestra clase Persona es modificar la
clase RepInternaPersona, contenida en un fichero distinto, que se convierte
as en un "cortafuegos" a efectos de recompilacin.

Bien, este es el momento en que el lector preguntar: a qu han venido


estos fuegos de artificio? Y la respuesta es ... ea! no podemos dejar que el
lector piense que C++ es un lenguaje fcil o lineal! Como he indicado en
repetidas ocasiones, en C++ los lmites se amplan y modifican da a da:
C++ es un lenguaje poblado de distintos idiomas, segn la terminologa ya
clsica de Jim Coplien, entendiendo por idiomas la conjuncin de ciertos
aproximaciones tericas y tcnicas que permiten unos esquemas lgicos y
formales de codificacin notablemente diferenciados entre s. As, al igual
que existe un "idioma de constructores virtuales" o un "idioma de
ejemplares" o un "idioma tipo Smalltalk", tambin existe un idioma que
podramos llamar de "modulacin protectiva en compilacin", con sus
lgicas ventajas y desventajas. Esta flexibilidad, para algunos malsana, es
una caracterstica que a nadie deja indiferente.

C++/OOP: UN ENFOQUE PRCTICO Pgina 86/297


DE LAS FUNCIONES
10
VIRTUALES

Conocemos que la herencia se implementa en C++ mediante la derivacin


de clases; hemos aprendido, tambin, los formalismos sintcticos para la
aplicacin de tal mecanismo; sabemos, al fin, de conversiones implcitas en
derivacin y del orden en la aplicacin de constructores y destructores. Y
ahora qu? Bien, esa es exactamente la cuestin: ahora entra en escena la
estrella, el epicentro de una nueva forma de programar: la funcin virtual.
Pero no adelantemos acontecimientos: seamos objetivos.

REUTILIZACIN Y REFINAMIENTO DEL CDIGO HEREDADO

Como ya vimos en el captulo anterior, el mecanismo de derivacin pblica


de clases nos permite implementar codificaciones "genricas" mediante una
sintaxis que los programadores de C podran fcilmente asociar, respetado el
debido distanciamiento, con la tcnica de uso de punteros a void. De esta
forma, si tenemos la siguiente jerarqua de clases:

class Empleado {
public:
int antiguedadEnLaEmpresa()
{
return antiguedad;
}
void estableceAntiguedad( int anos )
{
antiguedad = anos;
}
private:
int antiguedad;
// ...
};
class JefeDeSeccion : public Empleado { /* ... */ };
class Administrativo : public Empleado { /* ... */ };
class DirectorGeneral : public Empleado { /* ... */ };
// etc., etc.

C++/OOP: UN ENFOQUE PRCTICO Pgina 87/297


podemos codificar una funcin de uso "parcialmente genrico" tal como

void imprimeAntiguedad( Empleado* punteroAEmpleado )


{
cout << punteroAEmpleado->antiguedadEnLaEmpresa();
}

que nos permitir codificar fragmentos como el siguiente:

JefeDeSeccion* pFranklin;
Administrativo* pEleanor;
DirectorGeneral* pFrank;
// ...
imprimeAntiguedad( pFranklin ); // OK
imprimeAntiguedad( pEleanor ); // OK
imprimeAntiguedad( pFrank ); // OK

De esta manera se produce un doble ahorro: por un lado reutilizamos la


funcin imprimeAntiguedad, y por otro reutilizamos, tambin para toda la
jerarqua de clases, la funcin miembro antiguedadEnLaEmpresa. Perfecto?
Bien, esto se anima. Reutilizamos, en definitiva, en las clases derivadas
(pblicamente, claro!) cuanto tenemos en la clase base o cuanto a sta
pueda aplicarse. Qu pasara, sin embargo, si deseramos particularizar
directamente algunas de nuestras clases derivadas? Imaginemos una
situacin como la siguiente:

class DirectorGeneral : public Empleado {


public:
char* antiguedadEnLaEmpresa() const
{
return "Antiguedad sin importancia";
}
// ...
};

DirectorGeneral* pFederico = new DirectorGeneral;


pFederico->estableceAntiguedad( 3 );
imprimeAntiguedad( pFederico );

Puede el lector adivinar el resultado de la ltima lnea de cdigo? Parece


que, dado que se ha pasado a la funcin un puntero a un objeto del tipo
DirectorGeneral, deber ejecutarse la funcin miembro de esta clase, que
devolver una cadena cuando, en el mbito de nuestra funcin global, se
ejecute la lnea

cout << punteroAEmpleado->antiguedadEnLaEmpresa();

Correcto? No! Lo cierto es que, para desesperanza del lector, el resultado


es ... la impresin del entero "3"!. Qu ocurre aqu? Bueno, examinemos el
cdigo con ms detenimiento. Efectivamente se pasa a nuestra funcin

C++/OOP: UN ENFOQUE PRCTICO Pgina 88/297


global un puntero a un objeto de tipo DirectorGeneral, pero la funcin
espera en realidad un puntero a un objeto de tipo Empleado, por lo que,
merced a que la clase DirectorGeneral se deriva pblicamente de la clase
Empleado, el puntero a DirectorGeneral se convierte implcitamente en un
puntero a Empleado. O sea, el puntero, una vez "convertido", permite
acceder al objeto de tipo DirectorGeneral a travs de la porcin de ste
correspondiente a su clase base Empleado o, lo que es lo mismo, el objeto
apuntado por pFederico se trata como si fuera un objeto Empleado, y en
estos objetos la funcin antiguedadEnLaEmpresa devuelve un entero. Pero
quiz el lector lo habr de ver ms claro en el siguiente ejemplo
circunstancial:

DirectorGeneral* punteroADirectorGeneral = new DirectorGeneral;


punteroADirectorGeneral->estableceAntiguedad( 7 );
Empleado* punteroAEmpleado = punteroADirectorGeneral;

// la siguiente lnea devuelve "Antiguedad sin importancia"


punteroADirectorGeneral->antiguedadEnLaEmpresa();
punteroAEmpleado->antiguedadEnLaEmpresa(); // devuelve '7'

Esto es, se ejecutar la funcin correspondiente a la porcin de la clase


desde la que se acceda al objeto. Naturalmente si en la pertinente porcin no
existe la funcin, sta se buscar en la porcin de la clase de la que ste
pblicamente pudiera derivar, y si tampoco existe en sta se buscar en la
clase base pblica de la misma, y as recursivamente. La bsqueda de
miembros se realiza, pues, desde las clases derivadas hacia las clases bases,
y no al revs, lo que tiene una explicacin intuitiva muy clara: sabiendo que
la clase de un objeto deriva pblicamente de otra, si accedemos a un objeto
a travs de la porcin de una clase derivada se sabe que existir una
porcin, al menos, de la clase base en l, y en sta se buscar
seguidamente; si accedemos al objeto, en cambio, a travs de la porcin de
la clase base, el sistema no tiene forma de determinar si tal porcin
corresponde a un objeto de la clase base o de las derivadas, por lo que
siempre asume que el tipo del objeto corresponde a la clase base.

Observamos tambin, incidentalmente, un curioso y desagradable efecto que


Scott Meyers denomina "comportamiento esquizofrnico" en los objetos de
tipo DirectorGeneral: dependiendo desde donde se acceda a los mismos, el
comportamiento de stos ser distinto. Esto no es lgico! Cuidmonos,
pues, de redefinir en las clases derivadas las funciones heredadas de las
clases bases.

Vaya!, podra exclamar muy bien aqu el inquieto lector, ya entiendo: si


redefinimos una funcin heredada de una clase base en una clase derivada,
lo que ocurre es que se oculta la funcin de la clase base! Efectivamente,
inteligente lector. Pero, podra seguir el mismo lector, y si en lugar de
redefinir la funcin de la clase base lo que hacemos es sobrecargarla?
Tendremos entonces acceso a ambas? Vaya, cmo se nota que el lector

C++/OOP: UN ENFOQUE PRCTICO Pgina 89/297


aprende rpido y ya plantea difciles cuestiones. Vemoslo con un sencillo
ejemplo:

class DirectorGeneral : public Empleado {


public:
int antiguedadEnLaEmpresa( int* passnumber )
{
if ( passnumber == 17 )
return antiguedad;
else
return -1;
}
// ...
};

Parece que ahora, desde un objeto de tipo DirectorGeneral, podramos


acceder tanto a la funcin sin argumentos como a la funcin con un
argumento entero. Parece, parece, pero NO es:

DirectorGeneral miJefe;
miJefe.estableceAntiguedad( 5 );
miJefe.antiguedadEnLaEmpresa( 17 ); // OK: devuelve '5'
miJefe.antiguedadEnLaEmpresa(12 ); // OK: devuelve '-1'
// seguidamente intentamos acceder a la
// funcin heredada de la clase base
miJefe.antiguedadEnLaEmpresa(); // ERROR

Qu ocurre, de nuevo? Pues que no hay tal sobrecarga! Las funciones se


han declarado en diferentes mbitos, por lo que el nombre de la funcin con
un argumento esconde o "tapa" el nombre de la funcin sin argumentos. Si
deseramos todava, empero, realizar esta sobrecarga lo que deberamos
hacer es declarar ambas funciones en el mismo mbito, aadiendo la
siguiente funcin a la clase derivada:

inline int DirectorGeneral::antiguedadEnLaEmpresa()


{
Empleado::antiguedadEnLaEmpresa();
}

Vemos, pues, que, aparte del comportamiento esquizofrnico antes notado,


si redefinimos en una clase derivada una funcin de una clase base,
estaremos ocultando la funcin de la clase base y todas las sobrecargas de la
misma en la clase base.

SELECTORES DE TIPO

Pero volvamos al origen de nuestra disquisicin: lo que pretendamos era


poder usar de una codificacin genrica pero que, a la vez, nos permitiera
particularizar determinados mtodos para ciertas clases. Hemos visto que
esto no lo podemos hacer mediante punteros a la porcin de la clase base de

C++/OOP: UN ENFOQUE PRCTICO Pgina 90/297


los objetos, pues as nicamente accederamos a la funcin comn de la
clase base misma. Pero, y si realizramos un cast expreso al puntero para
que el objeto fuera accedido desde la porcin de clase adecuada? Perfecto!
Pero, cmo saber qu cast aplicar en cada caso? O sea, como podemos
saber, en el mbito de una funcin genrica para una determinada jerarqua,
a qu clase de objeto pertenece el objeto apuntado por un puntero ya
convertido a puntero a la clase base? Bueno, podramos conseguirlo
aadiendo a nuestras clases lo que se denomina un "campo selector de tipo".
Se trata, en definitiva, de un nuevo campo, comn a todas nuestras clases (y
por tanto candidato perfecto para la clase base), que tomar un valor
particular para cada clase, lo que nos permitir reconocer de qu clase es
cada objeto. En primer lugar, pues, veremos los cambios en la clase base:

class Empleado {
public:
enum tipoEmpleado{ JEFESEC, ADMTVO, DTORGEN }
tipoDeEmpleado() {
return valorTipoEmpleado;
}
char* lema() const
{ // lemas de las distintas categoras de empleados
// para ser particularizados en las clases derivadas
return "Los empleados son el alma de la empresa";
}
// ...
private:
tipoEmpleado valorTipoEmpleado;
// ...
};

Seguidamente tendramos que dotar a las clases derivadas de unos


constructores apropiados que inicialicen debidamente el campo selector de
tipo. Obviaremos, a este concreto fin, el cuerpo de los constructores,
resultando en algo as como lo siguiente:

class Administrativo : public Empleado {


public:
Administrativo() : valorTipoEmpleado( ADMTVO ) ...
// sigue lista inicializacin
{
// aqu viene el cuerpo del constructor
};
Administrativo( Administrativo& unAdmtvo )
: valorTipoEmpleado( ADMTVO ) ...
{
// cuerpo del constructor de copia
}
char* lema() const
{
return "Los Administrativos sostienen a la empresa";
}
// ...

C++/OOP: UN ENFOQUE PRCTICO Pgina 91/297


};

class DirectorGeneral : public Empleado {


public:
DirectorGeneral()
: valorTipoEmpleado( DTORGEN ) ... { /* ... */ }
DirectorGeneral( DirectorGeneral& miDG )
: valorTipoEmpleado( DTORGEN ) ... { /* ... */ }
char* lema() const {
return "El Director General es el corazn de la empresa";
}
// ...
};

class JefeDeSeccion : public Empleado {


public:
JefeDeSeccion()
: valorTipoEmpleado( JEFESEC ) ... { /* ... */ }
JefeDeSeccion( JefeDeSeccion& miJDS )
: valorTipoEmpleado( JEFESEC ) ... { /* ... */ }
char* lema() const {
return "Los Jefes de Seccin conducen realmente la empresa";
}
// ...
};

Ahora codifiquemos nuestra funcin "genrica":

void imprimeIdeario( Empleado* pEmpleado )


{
switch( pEmpleado->tipoDeEmpleado() ) {
case ADMTVO:
cout << (Administrativo* )pEmpleado->lema()
<< '\n';
break;
case JEFESEC:
cout << (JefeDeSeccion* )pEmpleado->lema()
<< '\n';
break;
case DTORGEN:
cout << ( DirectorGeneral* )pEmpleado->lema()
<< '\n';
break;
default:
cout << pEmpleado->lema() << '\n';
}

de forma que las siguientes lneas:

Empleado* juanNadie;
Administrativo* ruperez;
JefeDeSeccion* carvajal;
DirectorGeneral* deLasHeras;
// ...

C++/OOP: UN ENFOQUE PRCTICO Pgina 92/297


imprimeIdeario( juanNadie );
imprimeIdeario( ruperez );
imprimeIdeario( carvajal );
imprimeIdeario( deLasHeras );

originarn la siguiente salida:

Los Empleados son el alma de la empresa


Los Administrativos sostienen a la empresa
Los Jefes de Seccin conducen realmente la empresa
El Director General es el corazn de la empresa

Por supuesto que las particularizaciones podran haberse realizado tambin


con la intervencin de la representacin interna de los objetos, pero
vayamos a la esencia de lo expuesto: de una manera similar a la bien
conocida por los programadores de C, hemos creado una funcin que puede
"identificar" en tiempo de ejecucin los objetos de distintos tipos (en
jerarqua de clases) que se les pasen como argumentos, pero el sistema
observa algunas graves deficiencias y peligros. Pensemos, si no, en que, por
ejemplo, ya tuviramos una buena cantidad de funciones as codificadas (con
la aburrida letana de cdigo repetido que esto supone) y que, por
exigencias del problema en la vida real, debiramos anexar una nueva clase
derivada a nuestra jerarqua: tendramos, primero, que aadir un nuevo
enumerador a la clase Empleado; seguidamente deberamos adicionar
inicializadores para tal enumerador en los correspondientes constructores de
la clase derivada al efecto; habra que modificar tambin, por ltimo, todas
las funciones "genricas" aplicndoles un nuevo bloque case. Como el lector
ya habr adivinado, este proceso puede llegar a ser muy complicado,
enturbiando, de cualquier forma, la mantenibilidad del cdigo. Y desde
luego que no es esto lo que se espera de C++.

FUNCIONES VIRTUALES

No habra alguna manera de que fuera el mismo sistema el encargado de la


identificacin, en tiempo de ejecucin, de los objetos? O, lo que es lo
mismo, no podra el sistema generar por s mismo el cdigo necesario,
sobre la base conceptual expuesta, para procurar la identificacin necesaria
para as llamar a la funcin apropiada? Bien, s podra. De hecho esta
capacidad se denomina identificacin de operadores en tiempo de ejecucin,
y junto con el mecanismo de derivacin, permite en C++ la tan cacareada
Programacin Orientada-a-Objetos.

El mecanismo que usa C++ para procurar tal servicio es la funcin virtual.
En definitiva se trata de lo siguiente: si sabemos que una funcin de una
clase base va a ser particularizada o redefinida en una o ms clases
derivadas, esto se lo haremos saber al compilador notndole que se trata de
una funcin virtual. Hacindolo as, el compilador generar para la clase
cdigo adicional (algo as como el selector de tipo visto anteriormente) que

C++/OOP: UN ENFOQUE PRCTICO Pgina 93/297


permitir la asociacin de un objeto con la funcin (particularizada o no) que
le corresponda. Un momento, un momento! No estamos hablando aqu de
que el lenguaje C++ imbuya un mtodo de reconocimiento en tiempo de
ejecucin del tipo de los objetos. De hecho, la identificacin del tipo de los
objetos en tiempo de ejecucin es un tema actualmente en discusin,
basado en un trabajo recientemente presentado por Bjarne Stroustrup y
Dmitri Lenkov a X3J16. O sea, el lenguaje no proporciona mecanismos
(todava) para que un objeto pueda responder a la pregunta: de qu tipo
eres? Naturalmente podemos simularlo artificialmente, como ya hemos
visto, pero lo cierto es que en la comunidad C++ casi se ha instaurado como
norma el lema: "no es de buen estilo usar campos selectores de tipo en el
diseo de clases"27. As estn las cosas. Vaya! Entonces, si las funciones
virtuales no proporcionan la identificacin del tipo de los objetos en tiempo
de ejecucin, qu demonios hacen? Vemoslo desde el punto de vista de
Orientacin-a-Objetos: si recabamos en que un sistema software est
constituido por objetos y sus interrelaciones (los mensajes), vemos que
normalmente un mensaje dirigido a un objeto (la signatura de una funcin,
para entendernos) se asocia con un mtodo (una funcin miembro) en
tiempo de compilacin. Esto se denomina ligadura esttica, y permite la
optimizacin del cdigo, pues el compilador se asegura, mediante el
chequeo esttico de tipos, que el objeto a que se dirige el mensaje contiene
el mtodo apropiado para contestarlo, sealando un error en caso contrario.
Cuando estamos trabajando con clases con funciones miembros "normales",
el compilador no se preocupa de ms que de lo evidente. As, en el ejemplo
anterior:

void imprimeAntiguedad( Empleado* punteroAEmpleado )


{
cout << punteroAEmpleado->antiguedadEnLaEmpresa();
}

el sistema asocia, en el momento de la compilacin, el mensaje


antiguedadEnLaEmpresa al mtodo del mismo nombre... en la clase
Empleado!. El sistema no quiere saber nada, a efectos de la resolucin de
mensajes, de objetos de otros tipos, cuyas clases derivan de Empleado, pues
nada de ellos aparece en el cdigo expuesto: el mensaje se dirige a un
objeto Empleado a travs de un puntero al mismo, y punto. Pero, claro, no
es esto lo que queremos. Sigamos adelante, pues.

27
En el diseo del lenguaje C++ se sopes sobremanera la posibilidad de incluir
una tal identificacin en las clases, pero se estim que tal posibilidad abocara con
facilidad las codificaciones hacia estructuras de tipo switch, perjudicando la
modularidad y mantenibilidad de los programas. De hecho, las deficiencias
observadas en el lenguaje SIMULA derivadas de tal identificacin de tipos, que este
lenguaje s contempla, influyeron decisivamente en la decisin de no incluirlos en
C++.

C++/OOP: UN ENFOQUE PRCTICO Pgina 94/297


Existe, como el lector ya habr adivinado, otro mtodo de resolucin de
mensajes, denominado ligadura dinmica, que permite, en esencia, que un
mensaje pueda ser asociado con el mtodo de un determinado objeto en
tiempo de ejecucin. Esto se consigue en C++ si los mtodos a asociar son
virtuales. O sea, y volviendo al ejemplo anterior, si el mtodo
antiguedadEnLaEmpresa fuera virtual (a conseguir mediante una sencilla
sintaxis, como inmediatamente veremos), al sistema se le est diciendo que
codifique cierta informacin al construir los objetos de las clases que
contengan tal mtodo, de manera que sea l mismo el que se ocupe de
averiguar en qu clase debe buscar el mtodo (particularizado o no)
debidamente asociado al objeto, accedido bien mediante un puntero bien
mediante una referencia, que se pasa como argumento.

Bien, cmo se convierte un mtodo "normal" en "virtual"? Pues fcilmente:


antecediendo la declaracin de la funcin miembro en la clase base por la
palabra clase virtual. Volviendo a nuestro ejemplo de los lemas, lo nico
que tendramos que hacer es lo siguiente:

class Empleado {
public:
virtual char* lema() const
{
return "Los empleados son el alma de la empresa";
}
// ...
};

Con esta simple adicin (y una vez suprimido el cdigo generado por los
campos selectores de tipo), nuestra funcin "genrica" podra quedar
simplemente as:

void imprimeIdeario( Empleado* pEmpleado )


{
cout << pEmpleado->lema() << '\n';
}

de forma que las siguientes lneas:

Empleado* juanNadie;
Administrativo* ruperez;
JefeDeSeccion* carvajal;
DirectorGeneral* deLasHeras;
// ...
imprimeIdeario( juanNadie );
imprimeIdeario( ruperez );
imprimeIdeario( carvajal );
imprimeIdeario( deLasHeras );

originarn la siguiente salida:

Los Empleados son el alma de la empresa

C++/OOP: UN ENFOQUE PRCTICO Pgina 95/297


Los Administrativos sostienen a la empresa
Los Jefes de Seccin conducen realmente la empresa
El Director General es el corazn de la empresa

que es exactamente lo que desebamos. Vaya! Parece que ya se empieza a


ver el oro bajo las plomizas dificultades sintticas de C++: el cdigo de la
funcin se nos ha acortado drsticamente. Pero, qu pasara si tuviramos
que aadir a nuestra jerarqua una nueva clase con una redefinicin de
nuestra funcin virtual? Nada que nos deba preocupar! Vemoslo:

class Mensajero : public Empleado {


public:
char* lema() const {
return "Los Mensajeros son las arterias de la empresa";
}
// ...
};

Mensajero* juan;
// ...
imprimeIdeario( juan );

Qu imprimir la ltima lnea? Pues simplemente

Los Mensajeros son las arterias de la empresa

Notamos, en principio, que al declarar una funcin de una clase base como
virtual, automticamente las funciones con la misma signatura en las clases
derivadas pasan a ser virtuales. De esta forma la mantenibilidad del cdigo
se simplifica sobremanera.

En resumen: una funcin virtual es una funcin miembro (no puede ser
global) antecedida por la clave virtual en el protocolo de descripcin de su
clase, y tiene sentido cuando de la clase en la que se declara se derivarn
otras. La palabra clave virtual puede usarse en la declaracin de las
funciones virtuales en las clases derivadas, pero es redundante. Si una
funcin virtual no se redefine en una clase derivada, se asumir para sta la
definicin de tal funcin en su clase base. Una funcin virtual puede ser
declarada inline, pero esta declaracin nicamente deber tener efecto
cuando la llamada a tal funcin se resuelva, como es lgico, en tiempo de
compilacin.

De acuerdo: esto parece funcionar, pero cul es el mecanismo que subyace


bajo esta caracterstica del lenguaje? Intentar explicarlo: cuando el
compilador se encuentra con una clase que contiene una funcin virtual se
aaden al cdigo de la clase dada y de sus clases derivadas sendos punteros
a un array de punteros a funciones denominado tabla de funciones virtuales
o v-table, de manera que cualquier llamada a una funcin virtual se
resolver mediante un nivel adicional de indireccin: el paso por el array de
funciones virtuales. Este puntero incrementar el tamao de cada objeto, por

C++/OOP: UN ENFOQUE PRCTICO Pgina 96/297


un lado, a la vez que el mecanismo de indireccin, por otro, originar una
cierta penalizacin en la ejecucin de la llamada a la funcin virtual:
pequeas (y ya veremos por qu) desventajas para una gran facilidad. La
v-table poseer una entrada por cada una de las funciones virtuales definidas
en la jerarqua de clases en cuestin, y se alojar en un espacio de la clase
inaccesible al usuario, normalmente en una locacin esttica para permitir su
acceso a todos los objetos de la clase. En el ejemplo de empleados que
estamos tratanto, cada una de las distintas clases de la jerarqua construira
su propia tabla virtual, aadindose a las clases el cdigo necesario para
inicializar debidamente el puntero vptr de forma que apuntara a la v-table
apropiada. Como vemos, el sustrato conceptual es similar al usado con los
campos selectores de tipo.

CUESTIONES DE MBITO EN FUNCIONES VIRTUALES

Cuando trocamos una funcin miembro en virtual debemos recabar en que


algunos aspectos relacionados con la misma han cambiado.

Es conveniente que pensemos que la cualificacin de virtual se refiere a un


conjunto de funciones, ms que a una sola, compuesto por la declarada
expresamente como virtual y las que, con la misma signatura, pertenecen a
las clases derivadas pblicamente de la clase que alberga a la primera. De
alguna forma debemos pensar en una nica "plantilla" de funcin repartida
entre diferentes clases de una jerarqua: el sistema no sabe, en tiempo de
compilacin, a qu clase, incluyendo una funcin miembro virtual,
pertenecer un determinado objeto (no conoce, en definitiva, el cuerpo
concreto de la funcin a ejecutar), pero es evidente que debe conocer con
exactitud el prototipo exacto de la funcin virtual. Esto es, pasando al plano
de objetos, el mensaje es el mismo y nicamente cambia el mtodo, porque
si las funciones virtuales tuvieran distintos prototipos en las distintas clases a
las que pertenecen, cmo podramos codificar una llamada genrica a las
mismas? Lancmonos a un ejemplo: hemos declarado virtual la funcin
miembro lema() en la clase Empleado con el siguiente prototipo:

class Empleado {
public:
virtual char* lema() const {
// cuerpo de la funcin
}
// ...
};

Siempre que usemos tal funcin (o mejor "grupo de funciones") en nuestro


cdigo trataremos con una funcin sin argumentos, de nombre lema, que
devuelve un puntero a char. Imaginemos ahora que una determinada clase
desea extender la particularizacin de la funcin a su prototipo:

class Escribiente : public Empleado {

C++/OOP: UN ENFOQUE PRCTICO Pgina 97/297


public:
char* lema( TipoDeLetra fuente )
{ // se pasa como argumento un objeto
// del tipo TipoDeLetra
// imprime el lema con el tipo de letra "fuente"
}
// ...
};

Si recordamos el cuerpo de nuestra funcin genrica

void imprimeIdeario( Empleado* pEmpleado )


{
cout << pEmpleado->lema() << '\n';
}

preguntmonos ahora: cmo demonios esa funcin sin argumentos va a


llamar a una funcin con un argumento? Veamos qu pasa:

Empleado* cualquiera;
Empleado* unEmpleado = new Escribiente;
Escribiente* unEscribiente = new Escribiente;
// ...
imprimeIdeario( cualquiera ); // salida: Los Empleados son
// el alma de la empresa
imprimeIdeario( unEmpleado ); // salida: Los Empleados son
// el alma de la empresa
//
imprimeIdeario( unEscribiente ); // salida: Los Empleados son
// el alma de la empresa
TipoDeLetra courier;
// ...
unEscribiente->lema( courier ); // salida: Los Escribientes
// son la letra de la empresa
unEmpleado->lema( courier ); // error: no existe tal funcin
// en la clase Empleado

De igual manera como la "aparente sobrecarga" de una funcin no-virtual


(as denominaremos en adelante a las funciones miembros "normales") de
una clase base en una clase derivada origina el ocultamiento de la funcin de
la clase base y de sus sobrecargas, as ocurre con las funciones virtuales. Si
examinamos el cdigo notamos que la funcin lema(TipoDeLetra) de la clase
Escribiente es tratada como una funcin no-virtual, de tal forma que, como
ya sabemos, al ser accedido el objeto mediante un puntero a su clase base,
se ejecuta la funcin contenida en sta. Al cambiar, pues, el prototipo de la
funcin virtual en una clase derivada se ocultan las funciones con el mismo
nombre de la clase base, que son las que detentaran la cualificacin de
virtuales, por lo que tales no seran accesibles en el mbito de la clase
derivada.

C++/OOP: UN ENFOQUE PRCTICO Pgina 98/297


Qu pasara, sin embargo -podra inquirir el astuto lector-, si declarramos
nuestra funcin de prototipo modificado como virtual en la clase derivada?
Pues que en nada cambiara lo dicho: el nombre de tal funcin ocultara el
de la funcin virtual de su clase base, aunque podra aplicarse tambin como
virtual con efecto en las clases derivadas de, en nuestro caso, la clase
Escribiente. Habra que tener en cuenta, no obstante, que si en las clases
derivadas de nuestra clase derivada se redefine la funcin virtual de la clase
base, sta ocultar la funcin virtual de la clase derivada. Complicado?
Bueno, este galimatas lingustico podra expresarse as:

#define nl '\n'
#include <iostream.h>

class Base {
public:
virtual void f()
{
cout << "Base" << nl;
}
};

class Media : public Base {


public:
virtual int f( int numero )
{ // oculta la funcin virtual Base::f()
cout << "Media" << nl;
return numero;
}
};

class Derivada : public Media {


private:
void f()
{ // oculta la funcin virtual Media::f(int)
cout << "Derivada" << nl;
}
};

void g( Base* puntero )


{
puntero->f();
}

int main( int, char** )


{
Base* pBase = new Base;
Base* pBaseMedia = new Media;
Base* pBaseMediaDerivada = new Derivada;

Media* pMedia = new Media;


Media* pMediaDerivada = new Derivada;

C++/OOP: UN ENFOQUE PRCTICO Pgina 99/297


Derivada* pDerivada = new Derivada;

g( pBase );
g( pBaseMedia );
g( pBaseMediaDerivada );

g( pMedia );
g( pMediaDerivada );

g( pDerivada );

return 0;
}

Adivina el lector la salida de este sencillo programita? Vamos a ella:

Base
Base
Derivada
Base
Derivada
Derivada

Naturalmente! Es como si nuestra funcin virtual sin argumentos se hubiera


escondido, como el Guadiana, a su paso por la clase Media, para volver a
aparecer un poco ms alla en la escala de derivacin: en nuestra clase
Derivada. Notemos que en esta ltima clase parece que podra haber
conflicto con el nombre de dos funciones virtuales distintas, pero la cuestin
se salda a favor de la funcin virtual con ms profundidad en la escala
derivativa.

Queda establecido, pues, que las funciones virtuales redefinidas en las clases
derivadas de aqulla en la que se declara como virtual la primera, deben
compartir exactamente la misma signatura, lo que incluye el nombre de la
funcin, el nmero, tipo y orden de sus argumentos, y su tipo de retorno.
Queda entendido, tambin, que una modificacin de la signatura no
constituye una sobrecarga de la funcin virtual. Pero, y si lo que se cambia
es nicamente el tipo de retorno de la funcin virtual? Simplemente se
genera un error en compilacin. Algo s como:

class Media : public Base {


public:
int f() { // ERROR: el tipo de retorno no coincide
return 0;
}
};

CUALIFICACIN DE ACCESO DE LAS FUNCIONES VIRTUALES

C++/OOP: UN ENFOQUE PRCTICO Pgina 100/297


Incidentalmente podemos notar en nuestro ejemplo que la funcin declarada
virtual perteneca a la seccin pblica de la clase Base, aunque su
redefinicin en la clase Derivada ha sido incluida en la seccin privada de
esta ltima clase. Hemos podido, por otra parte, acceder sin problemas a
esta funcin virtual de la clase Derivada sin aparentes problemas. Qu
ocurre, pues, con los niveles de acceso para con las funciones virtuales?
Dependen nicamente de la cualificacin de acceso correspondiente a la
clase base en que se declarn como virtuales? O sea, en nuestro caso, por
ejemplo, la funcin virtual f() posee siempre acceso pblico por haber sido
declarada virtual en la seccin public de la clase Base? No! El nivel de acceso
de una funcin virtual queda determinado en cada caso por el nivel de
acceso que posee tal funcin en la clase a que pertenecen los punteros o
referencias a travs de los que se accede a la misma. Queda claro? Bueno,
repasemos el ejemplo: dnde se producen las invocaciones a la funcin
virtual f() en el cdigo anterior? Pues en el interior de la funcin g(Base*
puntero), concretamente en la lnea

puntero->f(); // OK: Base::f() es pblica

donde puntero, merced a la ya conocida conversin implcita de tipos entre


clases derivadas pblicamente, es siempre un puntero a Base,
independientemente del tipo concreto del puntero pasado como argumento
o an del tipo del objeto apuntado por el mismo. Imaginemos lo siguiente:

Media* punteroADerivada = new Derivada; // puntero a objeto


// de clase Derivada
g( punteroADerivada);

En este caso el puntero originalmente es de clase Media y apunta a un objeto


de clase Derivada. Sin embargo al pasarlo como argumento a la funcin
g(...), en el cuerpo de sta, se transforma en un puntero de tipo Base,
aunque sigue apuntando a un objeto de tipo Derivada. La funcin f() se
accede, pues, mediante un puntero de tipo Base*. Veamos, entonces, qu
cualificacin de acceso tiene la funcin f() en la clase Base: pblica, por lo
que en esta llamada de la funcin virtual el nivel de acceso es pblico. Si
intentramos acceder al mismo objeto (de tipo Derivada) a travs, por
ejemplo, de un puntero de tipo Derivada* , como en la clase Derivada el
nivel de accedo de la misma funcin virtual f() es private, el compilador
flagelara como error el siguiente cdigo:

Derivada* punteroADerivada = new Derivada;


punteroADerivada->f(); // ERROR: la funcin Derivada::f()
// es private

DESTRUCTORES VIRTUALES

Volvamos a nuestro anterior jerarqua de empleados y ponderemos la


siguiente situacin:

C++/OOP: UN ENFOQUE PRCTICO Pgina 101/297


Empleado* encargadoArchivo = new Administrativo;
// se utiliza el objeto de tipo Administrativo
// apuntado por encargadoArchivo
// ...
// seguidamente se desea destruir expresamente
// el objeto de tipo Administrativo
delete encargadoArchivo;

En principio la situacin parece elemental: construimos un objeto y, tras


usarlo, lo desechamos llamando a su destructor mediante el operador delete,
segn la sintaxis que ya conocemos. Correcto? No! No! Examinemos de
nuevo la ltima lnea: le pasamos un puntero al operador delete y
suponemos que tal operador llamar al destructor del objeto, pero qu es
lo que realmente conoce el operador delete de nuestro objeto? Nada! Tal
operador nicamente sabe de la clase a que pertenece el puntero que se le
pasa como argumento. Pero... la clase del puntero es distinta a la clase del
objeto que deseamos destruir! En definitiva, el compilador resuelve
estticamente la llamada al destructor, y efectivamente llama al destructor ...
de la clase Empleado! Pero desde luego que no es esto lo que desebamos.
Es ms: este esquema puede procurarnos ms de algn problema grave.
Recabemos por ejemplo, si no, en la posibilidad que el constructor de la
clase Administrativo use de la memoria del almacenamiento libre (quizs
para almacenar el nombre del empleado), ocupndose el destructor de la
misma clase de liberar tal memoria. Si ejecutamos el cdigo anterior,
empero, el destructor de Administrativo no ser llamado, con lo que tal
memoria no sera liberada: problema!. Pensemos por otra parte, tambin,
en un objeto de una clase derivada de otra, y sta de otra, y as
sucesivamente hasta llegar a una clase base de cuyo tipo ser el puntero
direccionado a nuestro objeto. Sabemos que en una jerarqua de clases los
destructores de las clases bases se ejecutan en orden inverso al de los
constructores, desde las clases ms derivadas hacia las clases bases. Si
merced a un cdigo similar al anterior se llama al destructor de la clase base
en vez de al de la clase de nuestro objeto, se estarn obviando los
destructores de las clases intermedias en la escala derivativa entrambas:
ms problemas!

Como ya habr adivinado el lector, tales problemas tienen solucin, y


singularmente fcil, a fe ma: debemos declarar los destructores como
virtuales. O sea, debemos declarar como virtual el destructor de la clase base
de la jerarqua derivativa. De esta manera el sistema, con el mismo cdigo
anterior, identificara en tiempo de ejecucin el destructor apropiado para el
tipo de objeto apuntado por nuestro puntero. Montemos, sin ms dilacin, el
ejemplo:

class Empleado {
public:
Empleado() { /* ... */ } // constructor
virtual void f() { /* ... */ }
// ...

C++/OOP: UN ENFOQUE PRCTICO Pgina 102/297


virtual ~Empleado() {} // destructor virtual
};

class Vendedor : public Empleado {


public:
Vendedor( String nombre ) { /* ... */ }
virtual void f() { /* ... */ }
// ...
~Vendedor() { // destructor virtual
delete nombre;
}
};

Empleado* pVendedorZonaCosta = new Vendedor;


// ...
delete pVendedorZonaCosta; // OK: llama a Vendedor::~Vendedor
// que a su vez llama a Empleado::~Empleado
// siguiendo el esquema ordinal del lenguaje

Un momento! Todo esto es perfecto, pero no haba quedado establecido


que las funciones virtuales deberan compartir exactamente la misma
signatura? Y no cambia, por definicin, el nombre del destructor en cada
clase? Naturalmente, pero este caso concreto es una necesaria excepcin a
tan rgida regla.

En realidad el problema con los destructores se nos puede presentar en


todas las jerarquas de clases, por lo que una buena regla sera: si de una
clase se derivan, o pueden llegar a derivarse, otras clases, debemos declarar
su destructor virtual.

Hay un caso, sin embargo, en el que los destructores virtuales no nos sern
de mucha utilidad: no podemos destruir un array de objetos de una clase
derivada mediante un puntero a su clase base, pues el compilador no tiene
forma de conocer el tamao de los objetos de la clase derivada, necesario
para calcular la situacin secuencial de los punteros this pasados al
destructor. Pero, bueno, no todo el monte es organo.

RESOLUCIN ESTTICA DE FUNCIONES VIRTUALES

Ya hemos visto cmo convertir funciones miembros ordinarias en virtuales.


Tales funciones conservan, no obstante, las caractersticas posedas por
cualquier funcin miembro: esto es, pueden ser invocadas directamente, sin
que entre en juego el mecanismo virtual. De hecho, en algunas ocasiones la
llamada a una funcin virtual necesariamente se resuelve en tiempo de
compilacin. Veamos un ejemplo con las distintas casusticas:

class Base {
public:
virtual void v() {
cout << "funcin Base::v()";

C++/OOP: UN ENFOQUE PRCTICO Pgina 103/297


}
Base() {}
Base( int sinImportancia ) {
v(); // resolucin esttica siempre
}
Base( Base* punteroABase ) {
punteroABase->v();
}
};

class Derivada : public Base {


public:
virtual void v() { //redundante e inelegante,
// aunque correcto (a evitar!)
cout << "funcin Derivada::v()";
}
Derivada() : Base() {}
Derivada( int n ) : Base( n ) {}
};

Derivada* punteroADerivada = new Derivada;


Base* punteroABase = new Derivada;
// la siguiente lnea imprime Derivada::v()
// por resolucin virtual
Base objetoBase( punteroADerivada );
// la siguiente lnea imprime Base::v()
// por resolucin esttica
Derivada objetoDerivada( 0 );
punteroABase->Base::v(); // esttica: imprime Base::v()
objetoBase.v(); // esttica: imprime Base::v()
( *punteroABase).v(); // virtual: imprime Derivada::v()

En esencia, la resolucin en tiempo de compilacin de las funciones virtuales


se reduce a los siguiente casos:
- si una funcin virtual se accede a travs de un objeto de una clase dada, se
ejecutar el cuerpo de la funcin correspondiente a dicha clase. Ntese en el
cdigo anterior la diferencia entre el acceso a travs de un objeto y el acceso
a travs de un puntero desreferenciado, pues este ltimo si usa del
mecanismo virtual.
- si el acceso se produce a travs de una referencia o un puntero, pero se
usa el operador de resolucin de mbito (::).
- si la invocacin se produce en el cuerpo de un constructor, llamado por un
objeto de una clase derivada. Esto se debe a que, debido al orden de
inicializacin de clases, cuando se construye un objeto de una clase derivada
primero se construye la porcin correspondiente a su clase base: o sea,
cuando se est ejecutando el constructor de la clase base todava no se ha
construido la porcin del objeto correspondiente a la clase derivada, y al no
existir esta porcin no puede llamarse, evidentemente, a funcin alguna de
la misma. Esto no quiere decir que cualquier funcin virtual en el cuerpo de
un constructor se resuelva estticamente. De hecho en el ejemplo anterior se
expone un caso en que no es as, y ello se debe a que el puntero a la clase
derivada, que se pasa como argumento al constructor de la clase base,

C++/OOP: UN ENFOQUE PRCTICO Pgina 104/297


apunta a un objeto ya construido, con lo que no se da la situacin
anteriormente expuesta.

El lector, si acaso, podra aqu inquirir: y si accedemos a una funcin virtual


"directamente" desde dentro de una funcin miembro?. Algo as como

void Derivada::unaFuncionMiembroCualquiera()
{
Base::funcionVirtual();
}

La respuesta? Bueno, habra que recordarle al lector que, en realidad, tal


funcin es accedida a travs del puntero implcito this y usando el operador
de resolucin de mbito, por lo que este caso se reduce al segundo
expuesto. Sin novedad, pues. Naturalmente, si suprimiramos la porcin
Base:: en el cdigo anterior, la resolucin volvera a ser dinmica (o virtual):
se tratara, en definitiva, de una funcin virtual accedida a travs de un
puntero (this).

FUNCIONES VIRTUALES PURAS Y CLASES BASES ABSTRACTAS

Como el lector habr podido apreciar, las funciones virtuales permiten aislar,
en la clase base de una jerarqua, el interfaz genrico comn a las clases
derivadas de sta. Volviendo a nuestro ejemplo primero y acercndonos ms
a la vida real, la clase base de nuestra jerarqua podra aparecer ms o
menos as:

class Empleado {
public:
Empleado();
Empleado( char* nombre );
virtual int nivelDeResponsabilidad();
virtual ~Empleado();
// ...
};

Lo que se pretende aqu es que cada objeto de la jerarqua responda con su


nivel propio de responsabilidad, y de ah la funcin virtual (ntese tambin
el destructor virtual, segn lo antes comentado). Pero vayamos un poco ms
alla: al declarar tal funcin como virtual, lo que estamos haciendo tambin
es proporcionar una implementacin por defecto para las clases derivadas
pues, como ya sabemos, si en una clase derivada no se redefine la funcin
virtual, una llamada a la misma producir la ejecucin de la correspondiente
a la clase base. Pero, es esto lo que deseamos? Es decir, existe un nivel de
responsabilidad por defecto? Parece que no, que ste debera ser
expresamente codificado para cada clase, pero lo cierto es que si un
desarrollador descuidado aade una clase a la escala derivativa y no redefine
tal funcin virtual, se heredar la implementacin de la clase base, de
manera que el resultado podra ser desastroso. Pensemos, por otro lado, en

C++/OOP: UN ENFOQUE PRCTICO Pgina 105/297


lo que hemos querido significar con la clase Empleado: esto es, por ir al
grano, tiene sentido la siguiente expresin?

Empleado unEmpleado;

Ms bien parece que no, pues cabra preguntarse: un empleado de qu


tipo? Nadie es un simple empleado, sin cualificacin absoluta! Quiz lo ms
apropiado sera impedir que tal clase se instanciara. Podramos, por
ejemplo, declarar sus constructores en la seccin protegida, de manera que
pudieran ser usados por sus clases derivadas, pero no pudieran usarse
directamente por los clientes de la clase para construir objetos. De esta
forma, sin embargo, podramos vulnerar tal esquema en las clases derivadas
para "exportar" al programa objetos del tipo prohibido.

Podemos, de alguna forma, solucionar los dos problemas expuestos? S,


con la ayuda de las funciones virtuales puras. Veamos cmo una funcin
virtual se convierte en pura:

class Empleado {
public:
virtual int nivelDeResponsabilidad() = 0;
// ...
};

Al declarar, mediante la sintaxis expuesta, una funcin como virtual pura en


una clase, se producen los siguientes efectos:
- No se podrn instanciar objetos de tal clase. Si ahora intentramos la lnea
en la que se construa un objeto de tipo Empleado, obtendramos un error
en tiempo de compilacin. As, por el simple hecho de contener al menos
una funcin virtual pura, una clase se convierte en una clase base abstracta o
ABC (Abstract Base Class).
- Si en una clase derivada no se redefine la funcin virtual, se heredar la
caracterstica de "puridad" de sta, convirtindose tal clase derivada en
abstracta. De esta forma nos aseguramos que todas las clases de las que se
puedan instanciar objetos redefinan, por fuerza, la funcin virtual de la clase
base.

Cabra inquirir aqu, dada la peculiar sintaxis vista, si las funciones virtuales
puras estn dispensadas de ofrecer una definicin. Bueno, en efecto lo estn,
dado que, como he explicado, cada clase con posibles instanciaciones
proveer su propio definicin. Podemos, sin embargo, dotar de definicin a
nuestra funcin en la clase base, de la misma forma que lo haramos con
una funcin miembro ordinaria:

int Empleado:: nivelDeResponsabilidad()


{
return 17;
}

C++/OOP: UN ENFOQUE PRCTICO Pgina 106/297


Esta definicin no sera heredada automticamente, empero, por las clases
derivadas, aunque s podr ser accedida por stas mediante el operdor de
resolucin de mbito:

class Secretario {
public:
int nivelDeResponsabilidad()
{ // redefinicin de la funcin virtual pura
return 13;
}
// ...
};

class Administrativo {
public:
int nivelDeResponsabilidad()
{ // uso de la definicin de la funcin virtual
// pura provista en la clase Base
Empleado::nivelDeResponsabilidad();
};
};

Tambin podra accederse tal definicin, usando del operador ::, a travs de
un objeto de clases derivadas, como por ejemplo:

Secretario secretarioDireccion;
secretarioDireccion.Empleado::nivelDeResponsabilidad(); //resolucin esttica

C++/OOP: UN ENFOQUE PRCTICO Pgina 107/297


CONSTRUCTORES VIRTUALES

Antes hemos visto que los destructores pueden ser declarados virtuales, y es
muy posible que el lector haya notado que nada a este respecto se dice de
los constructores. Veamos primero qu quiere expresar la idea de un
"constructor virtual". En OOP, y por ende en C++, debemos acostumbrarnos
a que los objetos sean autosuficientes: si mandamos un mensaje, se
ejecutar el mtodo apropiado de la clase apropiada al objeto. Las funciones
virtuales proveen buena parte de este "sabor polimrfico", estableciendo
procedimientos efectivos de identificacin, en tiempo de ejecucin, de las
funciones apropiadas a los mensajes lanzados en una jerarqua de clases.
Los destructores virtuales permiten, por otro lado, despreocuparnos de
codificaciones explcitas de desinicializacin de objetos. En perfecta secuencia
lgica, las funciones de carcter constructor y esencia polimrfica deberan
liberarnos de cdificar expresamente el tipo de objeto a construir. Es decir,
dado un objeto a ser construido en una determinada porcin de programa,
un constructor virtual de una jerarqua de clases permitira aplicar la funcin
constructora apropiada al objeto en cada caso a partir de una codificacin
general. Es como si dijramos: quien ms sabe de un objeto es el objeto en
s, as que ... constryete! Bien, esto es francamente interesante y suena casi
a mgico, pero volvamos a la Tierra: es curioso pensar que un lenguaje con
un muy fuerte chequeo de tipos, cual es C++, permita la creacin de una
codificacin con una absoluta relajacin de tal chequeo (pinsese que el tipo
de los objetos sera determinado en tiempo de ejecucin). Las posibilidades,
de cualquier forma, son indudablemente atractivas. Pero vayamos al grano:
C++ NO admite "constructores virtuales" (y aqu la comunidad Smalltalk
podra aumentar el tono y denostar, como es habitual, la muy cacareada
hibridez de C++ como OOP). Seamos ms precisos: el lenguaje C++ en s
no permite que los constructores puedan ser virtuales, pero es fcil construir
un esquema que pueda simular tal efecto. Bueno, puntualizando, es
Stroustrup quien dice que es fcil. Bsicamente la tcnica ms utilizada
consiste en disear una funcin (o modificar el cuerpo del constructor en la
clase base) que permita discernir el tipo de argumento para aplicar uno u
otro constructor y devolver un objeto apropiado. A un programador
tradicional esta tcnica le sugerira una estructura de tipo switch, aunque en
C++ lo suyo es resolverlo mediante funciones virtuales. No detallar ahora,
con todo, tales mecanismos, que el lector podr encontrar en el mismo
Stroustrup y mayormente en Coplien.

C++/OOP: UN ENFOQUE PRCTICO Pgina 108/297


11
CONCLUSIN

Bien, hasta aqu hemos revisado distintos aspectos del lenguaje C++ imbri-
cndolos con la programacin orientada-a-objetos, hasta llegar a lo que
parece su ncleo operativo: las funciones virtuales. Y es precisamente aqu
donde hemos de terminar esta introduccin. Por qu? Bien, precisamente
debido a ese carcter deliberadamente introductorio del texto. A partir de
este punto habra que considerar, al menos, los siguientes temas:

- herencia mltiple
- derivacin virtual
- plantillas ("templates")
- manejo de excepciones

Pero esto supone que, a fin de sacar partido a lo expuesto, deban


explicitarse cuestiones de estilo y ejemplos de limpia codificacin, y no una
mera exposicin de la sintaxis y caractersticas del lenguaje (cmo explicar,
sino, por ejemplo, la relajacin en los tipos de retorno de las funciones
virtuales en jerarquas de derivacin establecida en el cuasi-estndar ANSI, y
que ya se empieza a adoptar por compiladores comerciales como Borland
C++ 4.0? o la caracterstica de resolucin esttica de los parmetros por
defecto en funciones virtuales con resolucin dinmica?). Naturalmente tal
desarrollo habra de llevar muchsimas ms pginas de las posibles en esta
aproximacin al lenguaje y a la programacin orientada-a-objetos, por lo
que habr de quedar pospuesto, temporalmente, hasta la publicacin de una
nueva obra quizs denominada "Manual de estilo de C++". O quizs no.
Bueno, dejen que este autor repose un poco y ya veremos.

C++/OOP: UN ENFOQUE PRCTICO Pgina 109/297


A-1
DE LAS LIBRERAS
Y OTROS TILES

En el presente anexo hablar, tras haber revisado sucintamente en el libro la


teora del lenguaje, de los tiles prcticos que habran de permitir la
iniciacin en la programacin real aplicando lo ya explicado. Pero, qu
enfoque tomar? Esto es, debo presentar una lista comparativa, al estilo de
los frecuentes artculos comerciales al uso, en que se revisan distintos
productos comerciales y se les punta en razn de curiosos criterios,
pretendidamente objetivos? Bueno, la verdad es que creo que no es esto lo
que necesita el principiante en C++. As, lo que yo simplemente voy a
exponer es mi particular y subjetivsima visin de cmo debera abordarse el
uso de tales productos comerciales. De esta manera todos los tiles
expuestos en adelante sern revisados en razn de su conveniencia prctica
para iniciar al lector en el nuevo paradigma, y no por otras cualidades ms
tcnicas, propias de un segundo o tercer estadios en el aprendizaje del
lenguaje. Me explico: en filosofa, por ejemplo, ante las obras "El Ser y el
Tiempo", de Heidegger, y "Diferencia y Repeticin", de Deleuze, yo
recomendara para una etapa introductoria la ltima, obviando, en razn del
criterio elegido, las cualidades histricas y bsicas de la primera obra. No
recetar, pues, una bolsa de libreras de parecido cometido, sino nicamente
la que me merezca un inters diferencial. Desde luego que esta la larga
perorata tiene un nico fin: evidenciar que no hay recetas magistrales, y
menos en este campo. Manos a la obra.

C++/OOP: UN ENFOQUE PRCTICO Pgina 110/297


LIBRERAS DE CLASES

Una de las primeras ideas que se le intenta imbuir al principiante en C++ es


la siguiente: "No hay que reinventar la rueda". Es prcticamente seguro que
mucho de lo que se intenta implementar ya haya sido codificado por otros,
de manera que no se tenga que partir cada vez de la nada. Lo ms parecido
a esto son las libreras de funciones al estilo tradicional. Aqu, sin embargo,
operamos con objetos y, en C++, tambin con clases, por lo que las
libreras de C++ estn compuestas de clases: he ah la primera diferencia. La
segunda es significativa: aun cuando C++, mediante la derivacin de clases,
asegura la reutilizacin del cdigo sin disponer del fuente, lo cierto es que,
precisamente para favorecer la confianza del usuario, la disposicin de ste,
bien gratuitamente bien mediante un pequeo pago adicional, se ha
convertido es una costumbre establecida. Naturalmente esto tiene su razn:
aparte de las leyes que protegen la propiedad intelectual, quin habra de
molestarse en copiar y modificar unas clases dadas cuando, en un brevsimo
lapso de tiempo, stas sern mejoradas por la empresa que las dise,
dedicada fundamentalmente a este proceso, ofreciendo su actualizacin por
un mdico precio? La entrega del cdigo fuente intenta eliminar, por otra
parte, la lgica desconfianza inicial del usuario de la librera de clases: el
estudio de tal cdigo asegura la posible continuidad de su desarrollo en caso
que la empresa que lo cre desaparezca; se afianza, a la vez, el soporte en el
que posiblemente habr de asentarse la arquitectura software de una
empresa.

Dicho esto, subyace la cuestin tal y como la planteara el sufrido lector:


"Vale, vale. Pero, qu libreras debo utilizar? Bueno, en primer lugar hay
que decir que a ningn programador serio de C++ (a no ser que se dedique
a la confeccin de libreras de clases comerciales) se le ocurre implementar
arboles binarios, clases "string", tablas de hash, vectores, etc., para un
trabajo serio. La prctica totalidad de estas estructuras algebricas est
disponible en distintas libreras, a un precio muy ajustado y con una calidad
difcil de superar. No hay que perder de vista el hecho que las clases nos
permiten, mediante el mecanismo de herencia, refinar o modificar
selectivamente las clases en la librera usada, pues no estamos hablando de
funciones, como no me canso nunca de repetir.

Bien, existen dos tipos bsicos de libreras: por un lado estn las que parten
de una nica clase (libreras csmicas), normalmente denominada "Object",
y que suelen constituirse en los marcos de aplicacin que veremos un poco
ms adelante; por otro lado estn las libreras basadas en distintas clases. En
este apartado nos centraremos en libreras del ltimo tipo.

Antes de continuar, no debemos obviar lo evidente: la mayora de


compiladores comerciales (lisez Borland C++ 3.1, Visual C/++, Zortech
C++ 3.0, CA C++, Liant C++, etc.) vienen acompaados por distintas
libreras y marcos de aplicacin que intentan cubrir los distintos aspectos
susceptibles de aparecer en el desarrollo de una aplicacin. As, por ejemplo,

C++/OOP: UN ENFOQUE PRCTICO Pgina 111/297


Borland ofrece los entornos TurboVision, ObjectWindows, adems de una
librera de contenedores tipo Smalltalk, y una versin adicional de la misma
usando plantillas; mientras que Microsoft ofrece lo que denomina MFC's
(Microsoft Foundation Classes), para el desarrollo prctico de aplicaciones
Windows. No voy a entrar en detalles sobre estas libreras, suficientemente
documentadas en sus compiladores respectivos, pero s voy a enumerar
algunas apreciaciones al respecto:
en primer lugar, el hecho de que una empresa construya un buen
compilador no implica que la calidad de las libreras que distribuye sea
pareja. Ni siquiera quiere decir que sern las que mejor se ajustan al
compilador en concreto, pues suele ocurrir que otras libreras de firmas
independientes aprovechen mejor los recursos ofrecidos; por otro lado, hay
que notar que tales libreras suelen ofrecer (con alguna excepcin, como
"CommonView" de CA) bases para el desarrollo no-portable de aplicaciones,
pues atienden demasiado, en aras de una frecuentemente no bien entendida
eficiencia, a manejar los API's de los entornos para los que estn
especificamente diseadas. No es esto lo que un programador de C++ suele
esperar. No, al menos, al principio: por qu -podra pensar el lector- debo
imbuirme de conocimiento de MS-Windows para una aplicacin, y ms tarde
perder un tiempo precioso estudiando, por ejemplo, los API's de OS/2 para
poder migrar la anterior aplicacin a este sistema? He de inventar la rueda
cada vez que cambie de sistema o entorno grfico? Naturalmente que no!
No parece lgico que algo tericamente tan trillado como, verbigracia, el
manejo de ventanas en diferentes entornos, deba ser especficamente
manejado por el desarrollador. No estoy diciendo que el enfoque del tipo de
libreras expuesto no llegue a resultar efectivo en casos concretos, muchos o
pocos, pero s que difcilmente puede resultar didctico para el principiante.
Por esta razn, obviar su comentario.

Tools.h 5.1, de Rogue Wave Associates.

Nos encontramos ante una librera inteligente, portable y digna de estudio.


No he dicho, sin embargo, que sea la ms eficiente (pinsese que, por
ejemplo, existe una extensa librera denominada M++ para el clculo
matricial en C++). La presente versin incluye el manejo de plantillas
(templates). La extensa lista de clases, que comienzan por 'RW' (a excepcin
de las genricas, que comienzan por 'G'), incluye algunas como "RWBench"
(automatiza el proceso de testear -benchmarking- una porcin de cdigo),
"RWTimer" (para medir lapsos de tiempo real), "RWBtreeOnDisk" (para la
gestin de rboles binarios en disco"), "RWCString" y "RWCSubString" (la
versin Rogue Wave de la tpica clase "string"), "RWTStack<T,C>" (plantilla
de una pila de valores, donde 'T' representa el tipo de objetos en la pila,
mientras que 'C' se refiere a la implementacin de la pila: vector ordenado,
doble lista enlazada, etc.), y otras muchas ms. Veamos cmo se le puede
sacar partido a esta librera.

C++/OOP: UN ENFOQUE PRCTICO Pgina 112/297


Imaginemos que una empresa, Caos Informtico S.L. (en abreviatura C.I.),
necesita manejar objetos de tipo genrico "string". Lo ms inmediato sera
usar de forma directa la clase "RWCString", pero esto plantea sus propios
problemas: si Rogue Wave cambia el nombre de sus clases en futuras
versiones (como ha ocurrido con la nombrada, que antes se denominaba
"RWString"), o si deseamos cambiar de librera, nos encontraremos que
nuestro cdigo cliente de tales clases deber ser modificado. Pero esto no es
lgico. Cul es, pues, la solucin? Sencilla: debemos crear una clase
"nuestra" que sirva de interfaz entre nuestras aplicaciones y las libreras
elegidas:

class CIString : private RWCString {


public:
// constructores
// aqu se explicita el constructor
// adecuado de la clase base
CIString() : RWCString() {}
CIString( const char* cs ) : RWCString( cs ) {}
// ...
// seguidamente se hacen accesibles desde objetos de
// nuestra clase CIString diversas funciones de RWCString
RWCString::operator==;
RWCString::hash;
// ...
// aqu se cambia el interfaz de alfunas
// funciones de RWCString
void cambiaAMinusculas()
{
RWCString::toLower();
}
// ...
};

La verdad es que la clase "RWCString" consta de un nmero elevado de


mtodos propios (sesenta y cinco!), incluyendo indexacin, subcadenas, etc.
Hemos creado en un momento, pues, una clase para el manejo de cadenas
totalmente pulida, y adems, por la derivacin privada, hemos establecido
una barrera que impide el acceso al cdigo de la librera. Pero, un momento:
despus de una larga serie de captulos de introduccin a C++, el avieso
lector podra plantear: y si, en lugar de derivacin pblica, planteamos una
derivacin protegida? O tambin: y si trocamos la derivacin por "layering",
incluyendo un objeto de tipo "RWCString" en la seccin privada de nuestra
clase "CIString"? Pues que yo slo podra decir: Bravo, amigo lector! Se
nota que ya empieza a pensar antes de codificar! Veamos las dos cuestiones:
en cuanto a la primera, la derivacin protegida significa que el interfaz de la
clase "RWCString" y de sus correspondientes clases bases ser accesible en el
protocolo de las clases derivadas de nuestra clase "CIString", de forma que
otros componentes del equipo de desarrollo podran establecer subinterfaces
de uso de la librera, pero esto no parece, en nuestro caso, muy aconsejable;
la otra posibilidad, inteligente lector, se refiere a una cuestin que Tom
Cargill denomina "herencia innecesaria": ya que no vamos a favorecernos de

C++/OOP: UN ENFOQUE PRCTICO Pgina 113/297


las ventajas que el mecanismo de herencia proporciona, por qu entrar en
complicaciones?. La verdad es que el planteamiento de "layering" es el ms
prudente en este caso, y consistira en declarar en la seccin privada de
"CIString" un puntero, por ejemplo, a un objeto "RWCString", incluyendo
(pues ya no contamos con los mecanismos de construccin y destruccin en
derivacin) cdigo explcito para su construccin y posible destruccin en los
constructores y destructor de nuestra clase. El interfaz, por otra parte, sera
diseado con total libertad, conteniendo su implementacin mensajes al
objeto de tipo "RWCString" creado (usando de las funciones miembros de su
clase). Bien: todo esto es fcil y muy conveniente, as que, sin hacer muchos
ascos, lo dejar de elemental ejercicio para el lector, pues toda introduccin
que se precie ha de procurar trabajo improbo al principiante.

MARCOS DE APLICACIN

Un marco de aplicacin (application framework) es, simplemente, una


librera de clases que, ms que ayudar a soportar determinados procesos e
interacciones entre objetos, se establece como un "marco" que envuelve y
sirve de base a la totalidad del cdigo desarrollado. Estas libreras suelen ser
de tipo csmico y se destinan, principalmente, a la gestin de interfaces
grficos de usuario (GUI's). Precisamente debido a esto, una determinada
aplicacin normalmente har uso de tan slo un entorno de este tipo cada
vez. La orientacin "Smalltalk" se torna especialmente patente en estas
libreras especializadas, as como algunas de las tcnicas usadas en este
lenguaje.

C++/Views 2.0, de Liant Software Corporation, Framingham, MA.

Esta es una librera que yo recomiendo fervientemente, sin contraindica-


ciones, como una de las mejores herramientas comerciales existentes para la
iniciacin a la programacin orientada-a-objetos en C++. Se trata, en
esencia, de un marco de aplicacin para el desarrollo de programas de
gestin de GUI's usando C++, pero con algunas caractersticas que le
confieren un especial atractivo pedaggico:

- El mismo cdigo fuente puede ser portado sin modificaciones,


nicamente recompilando, a entornos como Microsoft Windows,
OS/2, OSF/Motif, Mac, etc.
- La librera est basada en el paradigma MVC (Modelo-Vista--
Controlador: Model-View-Controller), de claras facilidades para la
programacin en entornos grficos.
- El producto posee una utilidad grfica denominada "C++/Browse"
que permite el examen en jerarqua derivativa de clases del cdigo
fuente, antes de compilar, con una disposicin muy similar a la de
Smalltalk. Esta herramienta facilita grandemente la realizacin de
otras tareas comunes, como el "makefile", la gestin de dependencias

C++/OOP: UN ENFOQUE PRCTICO Pgina 114/297


de ficheros de cabecera, el manejo grfico de las jerarquas de clases,
etc.
- La derivacin grfica de clases, a travs de C++/Browse, es
especialmente didctica, pues refleja de forma evidente un mtodo de
reutilizacin de cdigo distinto al de las libreras tradicionales.
- Las herramientas que acompaan al paquete son especialmente
prcticas: una de ellas permite, por ejemplo, convertir los ficheros
script de tipo dialog, generados como DLG por el SDK de Windows o
una herramienta similar, en ficheros de cabecera C++ enlazadas con
la librera de clases, acercndose, de algn modo, a la programacin
visual portable. Otra utilidad facilita la autodocumentacin de las
clases generadas en la aplicacin.
- La documentacin del producto, salvados algunos inexplicables
errores, es bastante instructiva y completa.

Como quiera que parece que es un producto de este tipo el que ms pudiera
interesar, por la facilidad que aporta en el desarrollo de aplicaciones
Windows, a un lector de RMP, me extender suficientemente detallndolo.

C++/Views, en su versin 2.0 para MS-Windows, est compuesto por las


siguientes clases:

VObject
VAccelerator
Vassoc
VIntAssoc
VBitBltr
VBool
VBrush
VClipBoard
VCollection
VOrdCollect
VStack
VSet
VDictionary
VTokens
VContainer
VIntegerSet
VObjArray
VPointArray
VFont
VFrame
VGlobal
VClassTable
VMemory
VNotifier
VIcon
VIterator
VLocation
VMenu
VPopupMenu
VSysMenu

C++/OOP: UN ENFOQUE PRCTICO Pgina 115/297


VMenuItem
VMouseCursor
VPen
VPort
VRegion
VPolygon
VRectangle
VEllipse
VRoundRect
VString
Vstream
VFile
VArchiver
VSerial
VToFromStream
VTagStream
VTokenStream
VTimer
VDisplay
VBitMap
VPrinter
VWindow
VControl
VButton
VCheckBox
VRadioButton
VTriState
VPushButton
VIconButton
VGroup
VButtonGroup
VInclusiveGroup
VExclusiveGroup
VListBox
VComboBox
VMultiListBox
VScrollBar
VTextBox
VEditBox
VEditLine
VTextEditor
VDde
VDdeClient
VDdeServer
VMdiView
VView
VAppView
VMdiAppView
VControlView
VMdiView
VPopupWindow
VDialog
VAbout
VAddRemoveList

C++/OOP: UN ENFOQUE PRCTICO Pgina 116/297


VFileSelect
VInput
VListSelect
VMultiSelect
VReport
VYesNo
VEvent
VClass
VRatio
VWinInfo

Como puede inmediatamente apreciarse, existen clases predefinidas para la


mayora de los objetos que pueden aparecer en una tpica aplicacin grfica,
tales como dilogos SiNo (VYesNo), cajas para introduccin de lneas
(VInput), gestin de comunicaciones (VSerial), manejo de mens (VMenu),
etc. Perfecto, pero cmo se puede hacer uso de estas clases? Mediante
C++/Browse! Efectivamente, con esta herramienta tendramos en la pantalla
tres ventanas: una lista de clases, dispuestas grficamente en jerarqua, una
lista de mtodos, ordenados alfabticamente (y de aqu la conveniencia de
codificar el nombre de nuestras funciones en ingls), y un editor de texto. Lo
que se evidencia tambin enseguida es que los nombres de todas las clases
comienzan con 'V': esto es lgico y recomendable, pues la anterior versin
del producto mantena clases como "String" a secas, lo que ocasionaba
problemas al usar conjuntamente C++/Views con otras libreras de clases de
iguales ansias genericistas.

Vamos a construir, pues, como ejemplificacin de las facilidades de este


entorno de desarrollo, una ventana Windows con el texto "Hola C++" en
ella, como mandan los cnones de introduccin a lenguajes de programa-
cin.

Bien, qu queremos? Pues, en


primer lugar, construir una ventana
general de aplicacin. Si revisamos
la jerarqua de clases encontramos
que tales ventanas estn
representadas en la clase "VApp-
View", de manera que para
particularizar esta clase lo que
hacemos es derivar de la misma
una nueva clase. Pero, cmo
derivamos? Muy fcil: primero
seleccionamos en la Lista de Clases
la superclase, o clase de la que C++/Views: Creacin de una nueva aplicacin
queremos heredar (en este caso,
"VAppView"); seguidamente abrimos el men llamado "Classes" y
seleccionamos "Add Subclass": de inmediato aparece una caja de opciones
preguntando si la derivacin es pblica y/o virtual; sigue una caja de dilogo
inquiriendo el nombre de la nueva clase, que podra ser "HelloCPPView";

C++/OOP: UN ENFOQUE PRCTICO Pgina 117/297


despus el sistema nos pregunta el nombre del fichero en que se guardar
tal clase (una de las pocas restricciones de C++/Views es que C++/Browse
slo admite gestionar directamente ficheros que contengan una sola clase),
proporcionando un nombre por defecto ("hllcppvw") a travs de un mtodo
simplsimo de eliminacin ordenada de vocales. En seguida notaremos que
la nueva clase se ha incorporado grficamente en la Lista de Clases como
subclase de "AppView", a la vez que en la Lista de Mtodos han aparecido
automticamente tres funciones:

HelloCPPView()
~HelloCPPView()
boolean free()

Qu ha pasado? Pues nada: simplemente que el sistema, cada vez que se


crea una nueva clase, provee, en primer lugar unas ciertas funciones por
defecto, como son el constructor sin argumentos y el destructor, que, en
realidad, tienen el cuerpo vaco, por lo que parecen no hacer nada!, como si
fueran plantillas a ser rellenadas por el desarrollador, aunque, como en
seguida veremos, esto no es as. El sistema tambin provee una funcin con
el siguiente cdigo:

boolean HelloCPPView::free()
{
delete this;
return( TRUE );
}

tratndose, en el fondo, de una funcin virtual de cometido similar al del


operador "delete" de C++. Un poco despus veremos qu significa el valor
de retorno de esta funcin.

Bueno, ya hemos aadido nuestra


clase. Y ahora, qu? qu
hacemos para generar la ventana?
Lo cierto es que no tenemos nada
que hacer, pues la ventana de
nuestra aplicacin ya est codi-
ficada!! Y aqu el desprevenido
lector bien podra exclamar "Hay
algo que se me ha escapado?".
Bueno, quizs s. Repasemos
brevemente algo de lo su-
puestamente aprendido en este C++/Views: Lista de funciones heredadas
libro. La clase "HelloCPPView"
deriva pblicamente de
"VAppView", y esta ltima al construirse crea una ventana de aplicacin. Si
recordamos el orden de aplicacin de constructores en jerarquas de deriva-
cin, constataremos que se ejecutan primero los de las clases bases hasta
llegar a la ms derivada: de esta forma para construir un objeto de nuestra

C++/OOP: UN ENFOQUE PRCTICO Pgina 118/297


clase "HelloCPPView" se construir antes, resumiendo, la parte correspon-
diente a la clase "VAppiew" (o sea, una ventana de aplicacin) en aplicacin
de su constructor por defecto, y por ltimo se ejecutar el cuerpo del
constructor de nuestra clase (en este caso, vaco). Vemos con qu facilidad,
debido a los mecanismos estudiados, hemos construido una ventana, con
sus botones de maximizar y minimizar, y su men de sistema, barra de
ttulo y marzo redimensionable. Pero sigamos, porque la verdad es que no
deseamos una ventana estndar. Primero deseamos que la ventana disponga
de un ttulo apropiado, tal como "Ventana de Saludo" y, segundo, en ella
debe aparecer el texto "Hola C++". Bueno, la verdad es que esto es muy
fcil. El hecho que la ventana deba constar de ttulo lo asociamos de manera
evidente a la construccin de la misma, por lo que para lograr tal fin
nicamente deberemos aadir el cdigo apropiado al constructor de nuestra
nueva clase. As, seleccionando con el ratn el constructor por defecto en la
Lista de Mtodos, el cuerpo de ste (con un nico ';') aparece en la Ventana
del Editor (normalmente el Notepad), listo para ser modificado. Lo nico que
tenemos que hacer es incorporar en el constructor un mtodo para cambiar
el ttulo de la ventana. Y, de dnde sacamos tal mtodo? Bien, es fcil.
Tenemos dos opciones: si suponemos un suficiente conocimiento de la
librera de clases del producto, inmediatamente localizaremos en la clase
"VView" de la que "HelloCPPView" indirectamente deriva, la siguiente
funcin:

void setTitle( char *s );

y cuyo cometido es, con cierta lgica, establecer el ttulo de la vista a que se
aplica en la cadena apuntada por 's'; otra opcin es seleccionar, dentro del
men "Classes", la opcin "Inherits...", lo que provoca la aparicin de una
ventana de dilogo conteniendo una lista con todas las funciones miembros
heredadas por la clase actual (en nuestro caso, "HelloCPPView"), debiendo
buscar seguidamente una funcin del tipo necesitado (esto es, algo as como
"setWindowTitle" o "setViewTitle" o, definitivamente, "setTitle"), y
encontrando el mtodo anteriormente expuesto. Ahora lo nico que
tenemos que hacer es incluir tal mtodo en el constructor de nuestra clase,
de la siguiente forma:

HelloCPPView::HelloCPPView()
{
setTitle( "Ventana de saludo" );
}

de manera que una vez construida la ventana, en razn de la aplicacin de


los constructores de las porciones de las clases bases de la nuestra, entrar
en liza este ltimo constructor, cambiando su ttulo de la forma establecida.

Bueno, lo siguiente es aadir a la ventana la frase "Hola C++". Con la misma


secuencia anterior, encontramos en la clase "VWindow", de la que
"HelloCPPView" indirectamente deriva, la siguiente funcin:

C++/OOP: UN ENFOQUE PRCTICO Pgina 119/297


void wrtText( char *s, int x, int y );

cuyo cometido es escribir una cadena de tipo 'C' dentro del rea de cliente de
una ventana de tipo "VWindow" en la posicin (x,y). Parece que lo tenemos
claro: incluimos una llamada a esta funcin en nuestro anterior constructor y
... voil! c'est tout!. Diantre, no!! Recapacitemos ligeramente. Si incluimos
en el constructor este mtodo, cuando nuestra ventana se construya en ella
efectivamente aparecer la frase deseada, pero qu pasar cuando esta
ventana sea modificada o cubierta por otra y posteriormente descubierta? Y
aqu el lector podra pensar: "Pues que la frase seguir donde estaba, pues
de eso tiene que ocuparse el sistema". Bueno, algo de razn tiene el
confiado lector, pero tambin, en una buena medida, bastante de lgica le
falta: el sistema no tiene por qu saber si deseamos que tal frase
permanezca tras una modificacin de la ventana; o, ni siquiera, saber si debe
redimensionar el tipo de letra para adaptarlo al nuevo tamao de la ventana.
Bien, parece que antes de seguir debemos revisar el paradigma MVC en que
se basa esta librera.

La arquitectura de diseo de programas MVC (Modelo-Vista-Controlador),


proveniente de Smalltalk, en que se basa C++/Views, define la separacin
de la gestin de eventos de un programa (la capa de Control), la
presentacin de los datos de la aplicacin al usuario (la capa de la Vista) y el
modelado actual de los datos y procesos de la aplicacin (la capa del
Modelo). La interaccin entre las distintas capas funciona como sigue: el
Controlador enva mensajes a la Vista que, a su vez, ocasiona mensajes
direccionados al Controlador; el Modelo, por fin, es accedido y actualizado
mediante mensajes provenientes de la Vista. De esta forma se evidencia que
los objetos pertenecientes a la capa Modelo son pasivos con respecto al
sistema, accedidos nicamente en su relacin con ste a travs de los
objetos de la capa Vista, que mantienen una referencia a aqullos. El
Controlador, por ltimo, provee el interfaz entre dispositivos de entrada
(teclado, ratn, etc.), actividades de alto-nivel (eventos de movimiento,
arrastres del ratn, etc.) y las capas de Modelo y Vista. Veamos cmo
C++/Views implementa este paradigma.

- Controlador: la gestin del control de la aplicacin y de los eventos


se centraliza en la clase denominada "VNotifier". Existe un nico
objeto de tipo "VNotifier" para cada aplicacin, creado en el fichero
"globals.cpp" y al que apunta el identificador "notifier".
- Vista: la presentacin de una aplicacin recae en la jerarqua de
clases encabezada por la clase "VWindow", y se sustancia en controles
("VButton", "VListBox", VTextEditor", etc.), dilogos ("VYesNo",
"VAbout", "VFileSelect", etc.), mens ("VMenu", "VSysMenu", etc.),
estructuras de datos ("VStack", "VCollection", etc.) y ABC's (clases
bases abstractas) extensibles por el usuario ("VWindow",
"VPopupWindow", "VAppView", etc.).

C++/OOP: UN ENFOQUE PRCTICO Pgina 120/297


- Modelo: aqu vendran las clases especficas, implementadas por el
usuario, que encapsularan los algoritmos, procesos y datos de la
aplicacin a desarrollar.

Bien: examinemos, desde un punto


de vista global, cmo opera un tal
sistema. Lo que, en definitiva, se
est haciendo aqu es combinar la
programacin por eventos con la
programacin orientada-a-objetos,
de acuerdo con el siguiente
esquema: los eventos son
capturados por el objeto de tipo
"VNotifier", y son trocados por ste
en mensajes polimrficos (esto es,
funciones virtuales) que se dirigen
al objeto que actualmente retiene el C++/Views: Utilidad de conversin WinMkDlg
"focus" de la aplicacin, de tipo
derivado de "VWindow" (para que pueda funcionar en C++ el mecanismo
virtual). As, la clase "VWindow" y sus derivadas implementan, al menos,
una funcin virtual para responder a cada mensaje generado por el
"notifier", causado, a su vez, por un evento. A fin de permitir operaciones
con una adecuada cantidad de instancias de clases, las tablas virtuales son
colocadas, si es posible, en un segmento distinto. Hay que tener muy en
cuenta que un marco de aplicacin no es, por ejemplo, un simple "Windows-
Builder" o una herramienta lower-case (o pseudo-x-case) generadora de
cdigo para entornos grficos. Se trata en este caso, ms bien, de un
entorno para dotar de productividad a la programacin orientada-a-objetos
en C++ y, como proclama Liant, "todo lo que pueda hacerse en C++ puede
hacerse en C++/Views". La clase "VNotifier" provee tambin servicios a
clases del tipo genrico "VWindow" para controlar secuencias de eventos y
redirecciones teclado/ratn. Se trata, desde mi punto de vista, de uno de los
mecanismos ms elegantes para solucionar la a veces difcil combinacin de
programacin dirigida a eventos y OOP. Y piensen en la antiguedad
(Smalltalk-80) del paradigma. C++/Views proporciona al usuario, adems,
una herramienta utilsima denominada MKDLG en su versin DOS y
WINMKDLG en su versin Windows, que bsicamente consiste en un
conversor de ficheros script de tipo DLG, generados por el SDK o equivalen-
tes, a ficheros *.H y *.CPP: esto es, los cuadros de dilogo se generan de
forma visual, con una herramienta apropiada, y una vez que generamos el
fichero script correspondiente, C++/Views lo transforma en una clase de
tipo VDialog, con su pertinente ficheros de cabecera e implementacin,
enlazados con la librera de clases del entorno. Se consigue as una
semi-programacin visual.

Volvamos, sin entreternos ms, a lo prctico. Imaginemos que una ventana


cubierta queda descubierta, o que se maximiza una ventana minimizada:
qu pasa? Pues, entre otras cosas (como la adjudicacin del "focus", etc.),

C++/OOP: UN ENFOQUE PRCTICO Pgina 121/297


el "notifier" enva el mensaje "paint()" (pntate) a la ventana apropiada, espe-
rando que sta responda de la forma ms apropiada (mecanismo virtual).
Naturalmente la clase "VWindow" define el mtodo virtual de la siguiente
forma:

boolean VWindow::paint()
{
return ( FALSE );
}

estableciendo la respuesta por defecto al mensaje de "pntate", si la funcin


virtual no ha sido redefinida en la clase a que pertenece nuestra ventana
concreta. Vaya! Nos encontramos de nuevo con el retorno booleano. Vamos
a la explicacin: si el mtodo en cuestin consume el evento, debe retornar
TRUE, mientras que si no acta sobre el evento debe devolver un valor
FALSE, permitiendo as que el sistema subyacente aplique la respuesta por
defecto a tal evento.

Ahora podemos volver al mtodo "free()" en nuestra clase "HelloCPPView".


En principio debemos decir que la est funcin est originariamente definida
en la clase "VObject" con la misma codificacin que en nuestra clase. En
realidad vemos que se trata de un mecanismo de destruccin de objetos
que, por razones de la arquitectura del entorno, debe ser reimplementado en
cada clase, de manera que cuando el sistema enve el mensaje de liberacin
se destruya el objeto apropiado. Ahora se comprende que el sistema
codifique esta funcin por nosotros.

Bien, abordemos ahora el tema de la frase de saludo. Dado que el mensaje


"pntate" se enva por el "notifier" a una ventana tras su creacin o despus
de cualquier modificacin, lo procedente sera establecer un mtodo de
respuesta a tal mensaje que, simplemente, dibuje nuestro mensaje de
saludo en la ventana. Cmo hare-
mos esto en C++/Views? Mediante
la opcin de "aadir miembro" a
nuestra clase. Vemoslo:

boolean
HelloCPPView::paint()
{
wrtText( "Hola C++", 20, 20 );
return ( TRUE );
}

Mediante la ltima sentencia esta-


mos dicindole al sistema que
C++/Views: Adicin de un miembro a una clase
"consumimos" el evento, por lo que
no se debe aplicar accin alguna
adicional. Bien, pues ya est todo: ya tenemos la aplicacin. Un momento,
un momento! -podra intervenir aqu el infatigable lector-, dnde est la

C++/OOP: UN ENFOQUE PRCTICO Pgina 122/297


funcin principal?. Es cierto: cmo casi siempre el lector ha dado en el clavo.
Nos hace falta tal fichero con tal funcin, pero lo cierto es que el sistema ya
se ha ocupado de ello. En realidad C++/Browse provee una plantilla
genrica para la funcin principal (denominada "cvmain"), que se reduce a
las pocas lneas siguientes:

AppView *v = new AppView();


v->show();
notifier->start();
return TRUE;

ms los correspondientes archivos de cabecera. Lo que se exige al usuario es


que sustituya las menciones a "VAppView" por las de su clase especfica (en
nuestro caso "HelloCPPView"). Listar, a continuacin, los tres ficheros
generados:

// -------------------------------------------------------------
// HOLACPP.CPP
// -------------------------------------------------------------

#include "notifier.h"
#include "hllcppvw.h"

char *CTWindow = __FILE__;

#ifdef TURBO
int i;
#endif

int cvmain(int ac, char **av)


{
HelloCPPVView *v = new HelloCPPVView();
v->show();
notifier->start();
delete v;
return(TRUE);
}

// -------------------------------------------------------------
// HLLCPPVW.H
// -------------------------------------------------------------

#ifndef hllcppvw_h
#define hllcppvw_h
#include "appview.h"

CLASS HelloCPPView : public VAppView {


public:
VClass *iam();
boolean free();
HelloCPPView();
~HelloCPPView();

C++/OOP: UN ENFOQUE PRCTICO Pgina 123/297


boolean paint();
};

extern VClass *HelloCPPViewCls;


#endif /* hllcppvw_h */

// -------------------------------------------------------------
// HLLCPPVW.CPP
// -------------------------------------------------------------

#include "hllcppvw.h"

defineClass(HelloCPPView,VAppView)

HelloCPPView::HelloCPPView()
{
setTitle( "Ventana de Saludo" );
}

HelloCPPView::~HelloCPPView()
{
;
}

boolean HelloCPPView::free()
{
delete this;
return( TRUE );
}

boolean
HelloCPPView::paint()
{
wrtText(
"Hola C++", 20, 20 );
return( TRUE );
}

Y esto es todo. Pero lo mejor es


que el mismo cdigo, recompilado
con el entorno C++/Views apropia-
do, sirve para MS-Windows, OS/2,
OSF/Motif, etc. Es interesante notar, C++/Views: Editor de dependencias
tambin, que C++/Views genera
automticamente un fichero make para esta aplicacin. Ntese tambin que
cada vez que se aade una funcin miembro a una clase usando C++/Brow-
se, este incluye el prototipo de tal funcin en el archivo de cabecera de la
clase apropiada. El entorno, tambin, controla las dependencias de las clases
con respecto a sus ficheros de cabecera. En fin, un encanto de herramienta.

C++/OOP: UN ENFOQUE PRCTICO Pgina 124/297


GESTIN DE PERSISTENCIA

Bien, despus de considerar todo lo anterior, lo cierto es que en algn mo-


mento en el desarrollo de una aplicacin habr que tratar con el archivo y
recuperacin de los datos, o an mejor de los objetos. Mostrar dos distintas
posibilidades: una hbrida basada en el modelo relacional y otra sustanciada
como OODBS (Object-Oriented DataBase System).

Codebase++, de Sequiter Software Inc., Alberta, Canada.

Codebase++: Seleccin interactiva funciones miembros

No es sta una librera que yo aconseje sin antes despachar, como en el ms


comn de los medicamentos, una advertencia sustanciada en la frase: "sese
si no hay ms remedio". Lo que, en definitiva, intento explicar es que la
presente librera es, ms que una creacin originaria en C++, una
remodelacin de una exitosa librera de la misma empresa denominada
Codebase, codificada en C y destinada al manejo de ficheros de formato
DBF. El estudio detallado de las clases de esta librera evidencia que
simplemente se han aprovechado las mejoras del lenguaje C++ con
respecto a C, pero que no se ha aplicado la esencia constituyente del
paradigma de orientacin-a-objetos. De hecho, Sequiter pone ms nfasis en
sus productos en C que en los de C++. Bien, vayamos al grano. Para que el
lector pueda hacerse una idea, examine el siguiente programa, cuyo
cometido es eliminar la marca de borrado de los registros de una base de
datos relacional sustanciada en un fichero DBF:

#include "d4data.h"

extern unsigned _stklen = 10000; // salva bug de Borland C++

int main( int, char** )


{
// se crea un objeto de tipo CodeBase, que contiene
// los parmetros comunes a todo CodeBase++, mayormente
// sustanciados en el manejo de errores y flags.

C++/OOP: UN ENFOQUE PRCTICO Pgina 125/297


CodeBase cb;
// se asigna un identificador
// e inicializan variables internas
DataIndex miDBase( &cb );
// abre el fichero adecuado y lo asigna a la base de datos
miDBase.open( "FICHERO.DBF" );
// el siguiente bucle recorre la base de datos, desde el
// principio (top) hasta el final (eof),
// de registro en registro
for( miDBase.top(); !miDBase.eof(); miDBase.skip() )
// quita la marca de borrado de un registro
miDBase.recall();
return 0;
}

Aqu, por supuesto, vale lo dicho con respecto a la librera de Rogue Wave:
una vez decididos a usar CodeBase++, debe crearse una clase interfaz nueva
(quizs CIDBFM: Caos Informtico DataBase Files Manager) que encapsule
los mtodos de gestin necesitados: abrir, cerrar, indexar, filtrar, etc.

Poet, de BKS Software Entwicklungs GmbH, Berlin.

Poet: Browsers grficos y textuales de clases

C++/OOP: UN ENFOQUE PRCTICO Pgina 126/297


Esta es una herramienta ms apropiada que la anterior, segn el esquema
conceptual preconizado por la OOP, para dotar de persistencia a los objetos
de una aplicacin. Se trata, en sntesis, de un preprocesador denominado
PTXX que permite extender el lenguaje C++ mediante la adicin de palabras
clave para significar la persistencia. El desarrollador opera con un fichero de
cabecera con extensin HCD que, tras pasar por el preprocesador, genera un
fichero HXX en el que las palabras clave nuevas han generado relaciones de
derivacin, clases PTQuery, etc. El entorno, bajo Windows, cuenta con
browsers de clases grficos y textuales, que permiten la identificacin de
jerarquas, de clases persistentes, etc.

La sintaxis es transparente:

persistent class MiClase {


// ...
};

de tal forma que esta simple adicin asegura que la nueva clase dispondr,
entre otras cosas, de un mtodo "store()" para el archivo expreso de sus
instanciaciones.

PtBase baseDeObjetos;
// ...
// el preprocesador ha creado un nuevo constructor que toma
// por argumento un objeto PtBase
MiClase* objetoDeMiClase = new MiClase( baseDeObjetos );
objetoDeMiClase->store(); // archivo en la base de objetos
// ...
delete objetoDeMiClase;

a la vez que se han creado, por ejemplo, clases de queries semnticamente


asociadas a nuestra clase, a travs de los que podemos recuperar el o los
objetos deseados de una base de objetos:

// PTXX genera la clase [NOMBRECLASE]AllSet, que es una


// clase contenedora de objetos de tipo MiClase
MiClaseAllSet* allMisClases =
new MiClaseAllSet( baseDeObjetos );
MiClase* unObjeto;
// posiciona el contenedor al principio
allMisClases->Seek( 0, PTSTART );
// establece un bucle para revisar
// todos los objetos del contenedor
while ( allMisClases->Seek( 1, PTCURRENT ) == 0 )
{
// obtiene el objeto correspondiente
allMisClases->Get( unObjeto );
unObjeto->hazAlgo();
// seguidamente se descuenta una referencia de apuntadores
// al objeto y, si nadie lo usa, se destruye
allMisClases->Unget( unObjeto );

C++/OOP: UN ENFOQUE PRCTICO Pgina 127/297


}
delete allMisClases; // destruye el contenedor

El producto instala, por defecto,


tres ejemplos bsicos de uso del
motor de persistencia, entre los que
destaca una implementacin de una
"base de objetos" de "personas" y
"programadores", donde estos
ltimos forman un subconjunto de
aqullos. La ejemplificacin del
interfaz de introduccin y edicin de
los objetos es adecuada, y las facili-
dades provistas para las consultas,
mediante "queries" guiados es
suficientemente clara. Ciertamente Poet: Base de objetos "personas" y "programadores"
los interfaces grficos pudieran
hacer pensar que se trata aqu, sin
ms, de una tpica descomposicin
en tablas matizada por un barniz
comercial de orientacin a objetos.
Bueno: no es as, pero esto dice
mucho a favor de la capacidad
estandarizadora de tales interfaces.
Realmente los objetos de tipo
"programador", por ser subtipos de
"persona", asimilan, en la prctica,
las posibilidades, matizadas por las
cualificaciones de acceso, de estos
ltimos objetos. Se trata, en una Poet: Query sobre una base de objetos
conclusin informal, de la persis-
tencia de la herencia, donde los
objetos de tipo "programador" aprovecharan la implementacin de la
persistencia en los objetos "persona", dada la relacin de derivacin pblica
que une ambas clases. Este ejemplo, como los dos anteriores, es realmente
instructivo sobre la facilidad que herramientas de este tipo, y aun ms
potentes y no basadas en esquemas extensivos del lenguajes, pueden
procurar al desarrollador.

C++/OOP: UN ENFOQUE PRCTICO Pgina 128/297


Como fcilmente se puede apreciar,
el preprocesador realiza mucho
trabajo por nosotros, generando un
conjunto de clases que comparten
el nombre de la clase preprocesada
y que servirn para interactuar con
la base de objetos. Algunas de las
palabras claves nuevas son:
persistent, ondemand, transient,
depend, etc. POET aade, adems,
por sus caractersticas de
preprocesador, soporte para
plantillas (templates) independien- Poet: Compilador-preprocesador de persistencia
temente de si el compilador
concreto est capacitado para mantenerlas. Se trata, al fin, de un producto
muy interesante, multiplataforma y de precio muy ajustado. Un detalle ms
extenso de sus caractersticas, por basarse en conceptos no revisados en esta
libro (OODBS's en contraposicin al modelo relacional), no cabe en el pre-
sente anexo, aunque, por su inters, es merecedor, sin duda, de un captulo
diferencial.

C++/OOP: UN ENFOQUE PRCTICO Pgina 129/297


A-2
BIBLIOGRAFA
COMENTADA

El boom editorial generado en USA por la tecnologa de objetos ha poblado


las libreras de ese pas de ttulos en los que abundan los trminos "C++" y
"Orientado-a-Objetos". La confusin que tal profusin de obras puede causar
en el inadvertido lector es inevitablemente grande. La eleccin se torna difcil
y su resultado incierto. Ms del 50% de los textos son, sencillamente,
infames. Un 30% adicional son, simplemente, triviales. Un 15% ms se
limitan a repetir viejos conceptos aplicados a implementaciones particulares,
del tipo "Programe en C++ con Microsoft C/C++ 7.0", etc.. nicamente el
restante 5% contiene textos realmente interesantes, pero aun as es
importante evitar malgastar un tiempo precioso con obras que exponen
ideas bsicamente iguales. Vamos a iniciar, pues, un recorrido por los textos
de posible mayor inters para el lector. Una ltima advertencia: la totalidad
de las obras que aqu se van a detallar estn en ingls, aunque esto es algo a
lo que el programador de C++ debiera pronto acostumbrarse. Desafor-
tunadamente la escasa oferta editorial a este respecto en castellano no es, de
forma general, recomendable como opcin para los desarrolladores serios.
Prescindiendo de los mayormente impresentables manuales de uso de
compiladores, las actuales obras sobre C++ originales en nuestro idioma
son, en el momento de escribir este libro, a saber: una muy elemental
introduccin general a la OOP proporcionada por la obra "Programacin
Orientada-a-Objetos", cuya segunda parte ofrece un intrprete denominado
IMO con el que se pueden ensayar mensajes en calidad de "toy tool"; existe,
tambin, una elemental iniciacin a los tpicos ms frecuentes de la sintaxis
del lenguaje C++, escrita por F.J.Ceballos, referida a una versin claramente
desafasada del mismo y basada en un equivocado enfoque de diseo
top-down, suficientemente denostado y superado a estas alturas; existe, al
fin, una obra que s merece ser tenida en cuenta: "Programacin en C++",
de Enrique y Jos Hernndez Orallo, que, lejos de trasnochados esquemas
comerciales, ofrece un pedaggico y ejemplar panorama de introduccin al
"mero" C++, ajustado a AT&T C++ 3.0.

LIBROS BSICOS SOBRE C++

C++/OOP: UN ENFOQUE PRCTICO Pgina 130/297


Seguidamente se detallan las obras de introduccin al lenguaje, as como
aqullas de referencia bsica, sin las cuales no se debiera seguir avanzando
en el mismo. Naturalmente he debido, pues ste es el segmento editorial
ms poblado, tomar alguna decisin de exclusin que a alguno puede
extraar. No he incluido, por ejemplo, la famosa obra introductoria de
Dewhurst & Stark, pues no ofrece caractersticas diferenciales con respecto a
la de Lippman, mientras que sta me parece de mayor claridad pedaggica.
He procurado presentar estos textos en orden de dificultad creciente.

The C++ Workbook, por Richard S. Wiener & Lewis J. Pinson, 1990,
Addison-Wesley, 0-201-50930-X, 349 pg.

He de confesar que le profeso cierto cario a este texto, sin duda el ms


elemental de todos los aqu comentados. Se trata de una introduccin a
AT&T C++ 2.0, con multitud de ejemplos, letra grande y legible, y
abundantes resultados de programas. El libro se caracteriza por frecuentes
inserciones de apartados "Qu pasara si ...?", que intentan resolverle al
lector las dudas ms habituales en el primer acercamiento al nuevo lenguaje.
Teniendo en cuenta la proliferacin actual de desenfocadas introducciones al
lenguaje C++, repletas de dudosas intenciones y de tontera, esta obra es un
respiro que se puede completar en escasas horas. No sustituye, por
supuesto, a textos de introduccin como el de Lippman o el de Dewhurst &
Stark, pero muy bien podra constituirse en el perfecto puente hacia stos.

C++/OOP: UN ENFOQUE PRCTICO Pgina 131/297


C++ Primer, 2nd Edition, por Stanley B. Lippman, 1991, Addison-
Wesley, 0-201-54848-8, 614 pg.

Nos encontramos ante lo que podra ser calificado como el texto estndar
(por oficial) de introduccin a C++. En l se detalla la especificacin AT&T
C++ 3.0 (y, por tanto, plantillas y clases anidadas) de una forma
extremadamente rigurosa y pulida: los escassimos errores o carencias del
texto se han debido, como es posible apreciar en comp.lang.c++, a
circunstancias ajenas al autor. No se le supone al lector conocimiento previo
alguno de C++ (ni siquiera una media experiencia en C), y la exposicin
gradual de las caractersticas del lenguaje est perfectamente modulada. O
sea, se trata del libro ideal para iniciarse, sin la ayuda externa de un profesor
(aunque sta es, en general, de valiossima consideracin), en los tpicos y
recovecos del lenguaje. Lippman empieza muy pronto en la obra con la
codificacin de la clase "Array", y el lector puede asistir al proceso de su
refinamiento, mediante la aparicin en el texto y acertado comentario de
nuevas caractersticas que se le van aadiendo, involucrndose y
comprometindose poco a poco en el proceso. El libro est repleto de
instructivos ejercicios, de factible resolucin, a la vez que de ejemplos
"vivos": el lector ve, sorprendido, cmo stos van incorporando con extrema
facilidad nuevos detalles conforme avanza el relato. Todas las secciones del
libro muestran en la prctica, pues, los conceptos tericos que se
desprenden del lenguaje. Se exponen, por otro lado, una buena cantidad de
interesantes y bien probados trucos de programacin, que el lector podr
apreciar "en su salsa". Lippman dedica tambin un captulo a OOD (Diseo
Orientado-a-Objetos), sustanciado, como es habitual en el resto del libro, en
un ejemplo apropiado. La tcnica de OOD empleada es simplsima: se trata
de identificar las clases necesarias a nuestra aplicacin para dotarlas
seguidamente de los interfaces apropiados, y establecer las relaciones entre
ellas. Se trata, pues, ms que de una exposicin de OOD, una indicacin de
la importancia que se le debe conceder al uso, expreso o no, de los
conceptos de OOD a la codificacin en C++. En definitiva: este libro es
fundamental para el principiante, mientras que para el experto se convierte
en paradigma de cmo debe escribirse un buen texto de introduccin.

The C++ Programming Language, 2nd Edition, por Bjarne Stroustrup,


1991, Addison-Wesley, 0-201-53992-6, 669 pg.

Esta es la segunda edicin del texto que el Dr. Stroustrup public en 1.986
detallando el lenguaje C++, en su calidad de creador del mismo. Se trata de
un tutorial del lenguaje en el que, a diferencia del texto de Lippman, se
enfatizan los aspectos claves de uso del mismo. Se asume que el lector tiene
experiencia previa en programacin en C, y se detalla la especificacin AT&T
C++ 3.0 partiendo "de cero". El estilo del texto es enormemente sinttico,
de manera que la cantidad de tpicos revisados es netamente superior a la
del texto de Lippman. Comparada con la primera edicin, la obra ha crecido
tambin considerablemente en nmero de pginas. Precisamente ahora,

C++/OOP: UN ENFOQUE PRCTICO Pgina 132/297


cuando se empieza a generalizar el uso de plantillas (templates) y se
empieza a asentar la posibilidad prctica de uso del tratamiento de
excepciones, las pginas de este libro sobre tales materias se constituyen en
una insustituible guia referencial a la vez que conceptual, pues el
conocimiento de la mera sintaxis es claramente insuficiente. Si en la primera
edicin se revisaba la librera que entonces se denominaba "stream",
implementada por el propio Stroustrup, aqu se repasa la librera "iostream",
sobre las bases desarrolladas por Jerry Schwarz (que, por otro lado,
presenta continuas revisiones al comit ANSI C++). Se muestran y discuten
algunos interesantes trucos y tcnicas del lenguaje y el texto termina con un
manual de referencia. Una seccin importante del libro la constituye (120
pginas) la dedicada a OOD (Diseo Orientado-a-Objetos), en la que,
usando de una extensin de la tcnica de las fichas CRC (de la que el lector
encontrar una sucinta descripcin ms adelante, en el texto de Wirfs-Brock),
se introduce con acierto al lector en esta disciplina. Bien, se trata de un libro
muy difcil de resumir por su buscada tersedad conceptual y de estilo, pero
es indiscutible que, quiz con el soporte previo de una obra introductoria
como la de Lippman, su posesin es indispensable para cualquier
programador de C++. Estudinlo, sin ms condiciones.

C++/OOP: UN ENFOQUE PRCTICO Pgina 133/297


The Annotated C++ Reference Manual, por Margaret A. Ellis & Bjarne
Stroustrup, 1990, Addison-Wesley, 0-201-51459-1, 447 pg.

Esta es la biblia del C++: el texto de referencia indispensable para cualquier


estudioso, interesado o programador del lenguaje. Y biblia es aqu un
trmino usado con intencionada literalidad: acaloradas discusiones entre
miembros de la comunidad C++ suelen zanjarse con citas del tipo "la
seccin 13.2 de ARM (el apelativo carioso del texto) dice: ...", que surten el
efecto del mgico ensalmo que slo puede emanar de la autoridad por todos
reconocida. El texto de este libro fue tomado, en su momento, como
documento base por el comit ANSI C++ para la estandarizacin del
lenguaje, y la carencia, en estos momentos, de tal estndar ha redundado en
confirmar como inamovible tabla de salvacin, en el complicado mar
sintctico y conceptual de C++, a esta obra. Nos encontramos ante un texto
eminentemente referencial, que no pretende ensear al lector a usar los
mecanismos del lenguaje, sino ms bien a describirlos en una forma tersa y
rigurosa, pero que, fundamentalmente en las notas, explicita cuestiones
sobre la naturaleza de C++ que pueden llevar al lector a comprender mejor
la esencia del lenguaje: por qu tal o cual caracterstica no ha sido
contemplada, o por qu tal otra ha sido implementada de esta determinada
manera, o cmo podran suplirse comportamientos no previstos en el nudo
lenguaje, etc. Ntese que la primera lnea del prefacio dice as: "Este libro
provee una completa referencia del lenguaje para el usuario experto de
C++". No se pretende que el lector estudie de golpe, del principio al final, el
texto, pues tal tarea sera como la de intentar abarcar por orden alfabtico
una vasta enciclopedia (y esto trae reminisencias, no obstante, de aquel
"autodidacto" de la nausea sartriana): queda, pues, como una insustituible
obra de consulta.

LIBROS DE ESTILO EN C++

Tras superar la etapa introductoria y asimilar los recursos del lenguaje, lo


siguiente es aprender a codificar en C++ con efectividad y limpieza. Los
textos que se detalln a continuacin pretenden cubrir la etapa intermedia de
formacin del programador de C++, recalando ms que en la sintaxis en los
esquemas conceptuales que subyacen bajo sta.

Effective C++: 50 Specific Ways to Improve Your Programs and


Designs, por Scott Meyers, 1992, Addison-Wesley, 0-201-56364-9.

Este libro es, en esencia, una coleccin de 50 consejos sobre programacin


en C++, debidamente comentados y justificados. Pero no se trata de un
recetario al uso, sino ms bien de un compendio de lineas maestras,
interrelacionadas entre s (como el lector pronto descubre) y tendentes a
procurar al lector este tipo de conocimiento que subyace tras la seca sintaxis
del lenguaje. As, aunque el lenguaje permite usar con libertad la derivacin

C++/OOP: UN ENFOQUE PRCTICO Pgina 134/297


pblica, la aproximacin de ste al espritu de OOD exige que tal derivacin
se use solamente cuando se da una relacin de subtipo (o sea, de "ES-UN")
entre las clases a relacionar. El lenguaje permite, tambin, por ejemplo, la
redefinicin en clases derivadas de funciones miembros no-virtuales de las
clases base: esta prctica vulnera, sin embargo, como bien indica Meyers, la
cualidad de coherencia de los programas: el comportamiento de un objeto
variar dependiendo del puntero desde el que sea accedido. Como vemos,
los casos estudiados son de enorme utilidad prctica. Junto a stos se
exponen tcnicas que suelen pasar desapercibidas a los principiantes en
C++, como, por ejemplo, el chequeo de auto-asignacin en la
implementacin del operador de asignacin de una clase. As, nos
encontramos en el ndice con secciones como las siguientes: "Nunca
redefinas el valor heredado de un parmetro por defecto", "Cualifica los
destructores como virtuales en las clases base", "Chequea el valor de retorno
de new", "Evita los datos miembros en el interfaz pblico", para terminar con
"Lee el ARM". El libro, totalmente recomendable, proporciona al lector la
inteligencia de cmo usar los recursos sintcticos de C++ asimilados de los
textos introductorios clsicos. Adems, su lectura es tan amena como la de
una novela policial.

C++ Programming Style, por Tom Cargill, 1992, Prentice Hall, 0-201-
56365-7

Junto con el libro de Meyers, configura una de las mejores inversiones en


papel impreso que puedan realizar los ya iniciados en el lenguaje C++. El
enfoque adoptado por Cargill es, no obstante, distinto al de aqul: en vez
que acopiar reglas parciales y supuestamente independientes (como una
shopping list), el libro rene porciones de cdigo extradas de otros textos
de C++ y de productos comerciales, para luego examinarlos con el ojo
crtico del estilo. Es sorprendente la cantidad de elementos perturbadores, y
aun de graves errores, que Cargill hace aparecer ante nuestros ojos,
demasiado acostumbrados a las revisiones rutinarias y a los esquemas del
mnimo esfuerzo. Se tratan, as, temas como "Herencia innecesaria",
"Consistencia de clases", etc. analizando codificaciones de clases como
"String", "Stack", "Mquinas de Estados Finitos", etc. Cargill pretende imbuir
al lector de determinadas reglas de buen estilo en C++, tales como las
siguientes: "Identifica el delete para cada new", "No uses un constructor para
inicializar datos miembros estticos", "Considera los argumentos por defecto
como una alternativa a la sobrecarga de funciones", etc. Como se puede
fcilmente apreciar, el texto no tiene desperdicio. El tipo de conocimiento
que procura es, por otro lado, de una madurez matizadamente ms clara
que en el anterior texto, pues el lector adquiere una visin global de la
cohesividad del estilo a imprimir al cdigo. El mismo Scott Meyers reconoce
su proximidad a este texto, indispensable a todo programador de C++.

C++/OOP: UN ENFOQUE PRCTICO Pgina 135/297


C++ Programming Guidelines, por Thomas Plum & Dan Saks, 1991,
Plum Hall, 0-911537-10-4, 274 pg.

Los que experimenten cierta aversin por las colecciones de reglas y los
manuales de estilo, encontrarn en este libro el arquetipo de sus pesadillas.
Al menos en el formato. Se trata, en efecto, de secciones codificadas que
observan el siguiente esquema: TPICO (por ejemplo, 6.06 virtual_fct -
funciones virtuales), REGLA (descripcin de las lneas maestras de estilo
correspondientes al tema del ttulo), EJEMPLO (demostracin prctica de la
problemtica contextual en que se desarrolla la regla), JUSTIFICACIN
(argumentacin de apoyo, tanto mediante razonamiento directo como a
travs de informes y textos externos, de la regla expuesta), ALTERNATIVAS
(opciones de la regla consideradas menores, o aun impracticables),
NOTAS LOCALES (media pgina al menos, y normalmente pgina y media,
en blanco para el supuesto apunte de notas por el lector). Bien, decamos
que una tal rgida esquematizacin pudiera resultar demasiado restrictiva. El
presente texto es, sin embargo, enormemente provechoso. Muy en la lnea
de un anterior libro de los mismos autores ("C Programming Guidelines"),
sus distintas secciones tienden a reforzar una idea que los principiantes en
C++ rpidamente relegan al olvido: C++ no es C. C++ no es, tampoco,
Smalltalk o Eiffel. Se trata de sistematizar las soluciones a los cuellos de
botella en la gestin de proyectos en C++, fundamentalmente cuando
intervienen equipos de ms de dos personas: a este fin se explicitan
convenciones para nominar identificadores, facilidades para comentar
cdigo, gestin de sobrecargas, etc. El libro es, en definitiva, un compaero
a tener muy en cuenta ante el diseo efectivo de clases realmente cohesivas
y portables.

LIBROS DE PROGRAMACIN MEDIA Y AVANZADA EN C++

Este es un apartado singularmente despoblado en el actual panorama


editorial sobre C++. No hay que dejarse engaar, por supuesto, por ttulos
como "Inteligencia Artificial con Turbo C++" o "Fabrique su propia red
neuronal con Zortech C++ 3.0". Los tpicos avanzados del lenguaje
constituyen, en la mayora de los casos, un universo impensable por el lector
de introducciones. En este caso, pues, la seleccin ha resultado
evidentemente fcil. He preferido, con todo, reunir los textos bajo el epgrafe
comn "medios y avanzados" para evitar tener que calificar como avanzada
nicamente a la obra de Coplien (mi sincera opinin). En fin: lo mejor ser
que el lector juzgue y establezca su propia ordenacin.

A C++ Toolkit, por Jonathan S. Shapiro, 1990, Prentice Hall

Uno de las primeras necesidades de un desarrollador de C++ es la lectura y


el estudio de cdigo "prctico": tras una etapa de aprendizaje generalmente
no todo lo corta que uno hubiera esperado, el principiante llega a estar

C++/OOP: UN ENFOQUE PRCTICO Pgina 136/297


ciertamente hastiado de estudiar cdigo general, pedaggicamente aceptable
pero de nula o ligersima aplicacin en el mundo real. Este libro trata,
precisamente, de los componentes reutilizables que con la frase "no
reinventen la rueda" se suelen obviar en los textos introductorios de C++. La
obra, resultando agradablemente breve, ofrece, tras una corta aproximacin
a los conceptos muy bsicos de Orientacin-a-Objetos y OOD, cdigo de
herramientas tan utilizadas como listas enlazadas, rboles binarios, arrays
dinmicos, etc. El texto se acerca, desde la ptica del OOD, a la codificacin
de cada uno de estos componenentes, para ofrecer los listados completos en
la ltima parte del libro. Se proporcionan, tambin, algunas lneas para
mejorar distintos aspectos de los programas C++, aunque, por estar basado
en AT&T C++ 2.0, no incorpora, como sera sobremanera deseable,
plantillas (templates), suplindolas mediante macros genricas del
preprocesador. El autor expresamente autoriza el uso comercial de los
elementos software que aparecen en el libro, respetando el aviso de
copyright, y de su calidad comercial dan fe distintas aplicaciones bien
conocidas en las que aparece tal aviso. Con todo, y en contra de lo que el
lector pudiera esperar, el texto no acompaa diskette, por lo que al posible
usuario le esperan largas veladas de tedioso teclear. Naturalmente ste no es
un libro introductorio al lenguaje, pues se supone que el lector ya conoce
C++, sino, quizs, uno de los mejores candidatos para despegar de la etapa
inicial de aproximacin a C++.

C++/OOP: UN ENFOQUE PRCTICO Pgina 137/297


Advanced C++ Programming Styles and Idioms, por James O.
Coplien, 1992, Addison-Wesley, 0-201-54855-0.

C++/OOP: UN ENFOQUE PRCTICO Pgina 138/297


Este es uno de esos escasos libros que redimen, con sobriedad textual y
derroche de ingeligencia, el aluvin de tonteras y libros triviales sobre C++
y OOP que nos estn inundando de forma inmisericorde. Al Stevens ha
llegado a decir que este texto representa para C++ lo mismo que el de
Kernighan & Ritchie represent para C: bien, quiz este comentario se limite
a exteriorizar una pasin (fcil de comprender, por otra parte), porque sera
ms lgico aplicar tal comparacin al texto de Stroustrup, pero ciertamente
indica un cierto estado de nimo que permanece al terminar el texto. Siendo
este libro uno de mis preferidos, mi nica recomendacin es: cmprenlo!.
Pero, de que trata esta obra? De tcnicas ms o menos elaboradas de
codificacin en C++? Bien, no exactamente. Tras una breve, rigurosa y
modlica introduccin a los ms interesantes tpicos del lenguaje, el autor
nos anuncia de la existencia, dentro del lenguaje C++, de "idiomas"
autnomos: esto es, de abstracciones por encima del nivel sinttico
elemental del lenguaje y que configuran, a partir de distintos supuestos,
unas formas de codificar y un sustrato conceptual esencialmente distinto
entre cada una de ellas. Recordemos, por ejemplo, que Stroustrup nos
indica que los constructores en C++ no pueden ser virtuales, pero, a la vez,
expresa que con facilidad se puede suplir tal caracterstica. Coplien habla, sin
embargo, de un "idioma de constructores virtuales". Tomemos como
ejemplo este idioma para que el lector pueda entender mejor qu se
esconde tras tal denominacin. Un mensaje virtual permite posponer al
tiempo de ejecucin la ligadura entre prototipo e implementacin de la
funcin asociada; o sea, el mensaje se enviar a un objeto, desconocido en
tiempo de compilacin, y ste responder de la forma ms apropiada. Un
constructor virtual supone lo siguiente: a un objeto, cuyo tipo exacto
desconocemos, se le enva el mensaje "constryete", y ste eligir el mtodo
de construccin apropiado pues, quin habra de saber ms de construirse
que el objeto en s?. Mediante la estructuracin de una codificacin que
permita simular este comportamiento nos podramos encontrar, por
ejemplo, con que ninguno de los tipos de los objetos de nuestra aplicacin
sera chequeado en tiempo de compilacin, pues el tipo exacto de un
determinado objeto no nos hara falta ni siquiera para construirlo. Piense el
lector que, de un plumazo, nos hemos "cargado" una de las cacareadas
caractersticas de C++: el fuerte chequeo de tipos. Piense ahora el lector
cmo sera codificar en C++ sin el chequeo de tipos: muchas de nuestras
ideas predeterminadas habran de cambiar! muchsimo de nuestro cdigo ya
no tendra sentido! Es como si, de repente, estuviramos trabajando con un
dialecto de C++: con un "idioma". Coplien describe algunos idiomas
adicionales en su libro: "idioma de ejemplares", "idioma de estructuracin en
bloques", "idioma de lenguajes simblicos", "idioma de herencia dinmica
mltiple", etc. Ningn programador de C++ que aspire a algo ms que a la
codificacin de una lista enlazada debera dejar de leer este texto. Hay que
recabar, no obstante, que al lector se le supone un conocimiento adecuado
de AT&T C++ 3.0, versin en que la obra est basada. Una ltima nota: los
apndices son realmente interesantes, y convienen en procurar al lector un
agradable sensacin de efectividad.

C++/OOP: UN ENFOQUE PRCTICO Pgina 139/297


Data Abstraction and Object-Oriented Programming in C++, por
Keith E. Gorlen, Sanford M. Orlow & Perry S. Plexico, 1990, John Wiley &
Sons, 0-471-92346-X, 403 pg.

Este texto est basado en el estudio realizado por los autores para proveer
de optimizaciones software a los sistemas UNIX de los Institutos Nacionales
de Salud (NIH) en USA. Se trata, en esencia, de una librera de clases
envolvente (conocida como librera NIH) modelada a imagen y semejanza
del entorno Smalltalk-80 y prescindiendo de las capacidades grficas de ste.
El libro exige un solvente conocimiento previo de C++ y se apoya en AT&T
C++ 2.0. Usando la terminologa de Coplien, podramos decir que aqu se
genera un "idioma tipo Smalltalk", y lo cierto es que lo que en el texto se
expone ha tenido una extraordinaria y reconocida influencia en el desarrollo
de un gran conjunto de entornos de aplicacin y libreras de clases
genricas. El provecho que los programadores de C++ pueden extraer de la
obra es evidente, pues sta est montada como un tutorial, explicando
detalles y decisiones de diseo de las clases (algunas de ellas inapreciables
para la adecuada construccin de componentes reutilizables, una de las
expectativas ms cacareadas y, a la vez, ms difciles de C++) y, tras esto,
directamente utiliza el entorno creado como herramienta de desarrollo,
explicando la posible extensin del sistema. La librera NIH incluye clases
como Iterador, Bolsa, Diccionario, Tiempo, Vector, Fecha, Coleccin, Pila,
Objeto (la raz de la librera "csmica", pues tal es el nombre por el que se
conocen las libreras con una nica clase base inicial). Como el lector puede
fcilmente intuir, este tipo de libreras propende al usuario a practicar la
derivacin mltiple, y dadas las dificultades no siempre evidentes que esto
entraa, el libro dedica un largo captulo a este respecto. Se detalla, incluso,
un ejemplo de aplicacin de base de datos usando la librera. Mi
sugerencia? Bien, el fuerte entroncamiento de los conceptos de OOD con el
acertado uso de C++ procuran un excelente texto de uso referencial, de
valiossima lectura para cualquier programador de C++ y aun de los
estudiosos de OOP; las ventajas y defectos del enfoque tipo Smalltalk
adoptado, por otro lado, disgustarn o deleitarn al lector, dependiendo en
buena medida de su estilo y costumbres, pero esto aguzar,
alternativamente, el ojo crtico o el gozo del lector. Lanlo!

C++ Strategies and Tactics, por Robert B. Murray, 1993, Addi-


son-Wesley, 0-201-56382-7, 273 pg.

Como el mismo ttulo indica, Bob Murray, editor durante muchos aos de
C++ Report, ha querido mostrar, como en ajedrez, las estrategias y compo-
nendas que trascienden los meros movimientos de las piezas del juego. No
es exactamente, en general, un libro "avanzado" sobre el lenguaje, sino que
ms bien ocupa ese estadio intermedio entre los libros de estilo y la
practicidad del cdigo realmente efectivo. En este sentido los dos captulos
dedicados a las plantillas ("templates") justificaran, por s solos, la lectura

C++/OOP: UN ENFOQUE PRCTICO Pgina 140/297


del libro, pues dada la novedad comercial de esta adicin al lenguaje, pocos
textos se han ocupado hasta la fecha de revisar, de forma seria, algunos
aspectos no triviales de la misma. Junto con las plantillas, el buen diseo de
jerarquas de herencia en C++ y la reusabilidad del cdigo ocupan el grueso
de la obra, que, adems, procura un captulo sobre el manejo de
excepciones. Nos encontramos, pues, ante una obra moderna y de potente
claridad, pensada para el lector no-experto y que contiene ejercicios,
resmenes y una buena cantidad de ideas y sugerencias de buen diseo en
C++. En definitiva, una adicin indispensable para la biblioteca de C++.

LIBROS SOBRE SOFTWARE ORIENTADO-A-OBJETOS

El lector podra preguntarse aqu: realmente necesito de textos sobre ideas


generales de la orientacin-a-objetos? Qu ayuda me pueden prestar tales
ideas en los procesos diarios de codificacin en C++? Bueno, recordemos
que C++ es un lenguaje con facilidades para la Programacin
Orientada-a-Objetos, y que tal es, en definitiva, la fase de implementacin,
tras las fases de diseo y anlisis orientados-a-objetos, de los objetos y sus
relaciones modelados en base a los conceptos de orientacin-a-objetos a
travs de los que se matiza y visiona nuestro problema en el mundo real.
Las ideas generales de este nuevo paradigma nos pueden ayudar,
normalmente de forma inestimable, a encauzar nuestras codificaciones hacia
modelos conceptuales ms adecuados a la nueva orientacin, consiguiendo
que nuestro software sea ms robusto y fiable.

C++/OOP: UN ENFOQUE PRCTICO Pgina 141/297


Object-Oriented Software Construction, por Bertrand Meyer, 1988,
Prentice Hall, 0-13-629031-0,534 pg.

El presente trabajo es, sin duda, la ms rigurosa, acertada e inteligente


exposicin de los principios en los que se sustentan los sistemas
orientados-a-objetos. A la vez, y esto puede desconcertar al lector
inadvertido, es una primera descripcin del lenguaje de programacin Eiffel,
del que el Dr. Meyer es directo arquitecto y creador. Pero empecemos con
mtodo. La primera parte, "Ideas y Principios", es absolutamente impagable:
lo que en ella se expone, sustanciado en cinco criterios y seis principios, es
frecuentemente usado por m mismo en buena parte de los cursos de OOA
& OOD que imparto. La exposicin es sorprendentemente concisa, de forma
que la revisin de conceptos supuestamente conocidos por el lector se torna
en extremo interesante. Tras este anlisis general de los sistemas orientados
a objetos, el Dr. Meyer se cuestiona por un lenguaje con facilidades para su
implementacin, y como quiera que, segn sus propias palabras, no
encuentra ninguno, decide crear el suyo propio, sujeto con exactitud al
paradigma de objetos. Aparece as Eiffel, lenguaje orientado a objetos puro
donde los haya, de sintaxis tipo Pascal e interesantsimas caractersticas.
Pero, interesa esto al programador de C++? As lo creo. El Dr. Meyer no se
limita a describir el lenguaje, sino que, como artfice del mismo, explica las
disyuntivas en las decisiones de diseo y justifica las medidas adoptadas en
cada caso, trayendo a colacin interesantes problemas presentes en muchos
de los diseos orientados-a-objetos. Como ejemplo sirva el tratamiento que
en el lenguaje se dan a lo que se denominan "precondiciones" y
"postcondiciones": su claridad conceptual ha redundado en que, en aras de
la modularidad, limpieza y coherencia del cdigo, tal enfoque haya sido
posteriormente adoptado por distintas libreras de C++, como, verbigracia,
"Tools.h++" de Rogue Wave. Una tercera parte del texto se ocupa de
revisiones genricas de lenguajes clsicos de programacin, as como de sus
extensiones a objetos, y aun de C++, Smalltalk, Ada, etc. Se revisan
tambin cuestiones de herencia y, de forma leve, cuestiones como la
persistencia de objetos, que en el momento de publicacin de esta edicin
no estaban todava en el ojo del huracn, como ocurre ahora. En los
apndices se retoma, por fin, el lenguaje Eiffel a modo de fragmentos
referenciales. Se trata, en resumen, de un libro indispensable para cualquier
con pretensiones mnimamente serias en el mbito de la OOP: reserven,
pues, un hueco en su estante para l. O mejor an: para su segunda
edicin, ya disponible en estos momentos.

C++/OOP: UN ENFOQUE PRCTICO Pgina 142/297


A Book of Object-Oriented Knowledge, por Brian Henderson-Sellers,
1991, Prentice Hall, 0-13-059445-8, 297 pg.

El presente libro podra ser considerado como una eficaz introduccin al


panorama general de orientacin-a-objetos y, aunque, segn informa el
propio autor, el texto pretende ser una guia en el proceso de formacin y
aclimatacin de la mentalidad de los lectores al paradigma de objetos (para
lo que incluye modelos de transparencias "ad hoc" que pueden ser copiadas
y utilizadas como ilustracin de cursos sobre la materia), la obra se
constituye, en el fondo, en una esclarecedora revisin, desde una adecuada
distancia (y recurdese aqu la frase de Ortega sobre Cleopatra), de los
distintos criterios que pueblan, a veces en descorazonador desorden, el
universo de los objetos. Se revisan, as, metodologas de anlisis, diseo e
implementacin, intentando procurar al lector una suerte de plataforma
conceptual bsica desde la que pueda acceder con mayor comodidad a
tcnicas concretas.

Object-Oriented Methods, por Ian M. Graham, 1991, Addison-Wesley, 0-


201-65621-8, 410 pg.

Es ste un libro caracterizado por la perspectiva globalizadora bajo la que se


contemplan las ideas y conceptos de orientacin-a-objetos. La impronta
pragmtica britnica se deja notar, y su lectura es realmente amena. El texto
comienza con una bien entramada introduccin crono-sectorial a los tpicos
de la OOT: conceptos bsicos, lenguajes de programacin orientados-a-
objetos, WIMPs, bases de datos relacionales, bases de datos orientadas-a-
objetos, etc.; hasta llegar a la parte ms significativa: anlisis y diseo
orientados-a-objetos. Aqu Graham, tras una revisin crtica de algunos
mtodos (HOOD, Coad/Yourdon, etc.), expone la metodologa desarrollada
en BIS Applied Systems: SOMA, una variacin de Coad/Yourdon a la que se
han aadido, simplificando, "triggers". La orientacin-a-objetos es filtrada a
travs de la experiencia del autor en los campos de inteligencia artificial e
ingeniera del conocimiento y, as, resulta curiosa y enormemente
instructiva, por ejemplo, la facilidad con que Graham aborda la fase de
identificacin de objetos y de sus relaciones. La prototipacin aparece
descrita, seguidamente, de una forma esclarecedora, para terminar con un
vistazo al futuro posible y un muy interesante apndice sobre "objetos
borrosos". En fin, se trata de un texto muy aconsejable para aqullos que
busquen una visin integradora de las nuevas tcnicas en el continuum de la
evolucin informtica. No est de ms, al fin, probar un poco de solidez
europea frente a las montaas (magnficas, por otro lado) norteamericanas.

Object-Oriented Programming, por Peter Coad y Jill Nicola, 1993,


Prentice Hall-Yourdon Press, 0-13-032616-X, 582 pgs. y disquete incluido.

C++/OOP: UN ENFOQUE PRCTICO Pgina 143/297


Bueno, en la misma tnica que otros libros firmados en colaboracin por
Peter Coad, es ste un texto divertido, ameno y claramente pedaggico. Tras
el ndice, de increble longitud, el lector se encuentra con una obra
estructurada en cuatro partes, sustanciadas cada una de ellas en un ejemplo
completo y en orden creciente de dificultad: "Contador", "Una maquina
expendedora", "ventas, ventas, ventas" y "Sigue el flujo", seguido por
apndices en que se detallan las caractersticas esenciales de los lenguajes en
que se desarrollan tales ejemplos: C++ y Smalltalk. Para el anlisis y diseo
de stos se sigue, cmo no, el mtodo y la notacin de Coad/Yourdon, y
cada paso es suficientemente explicado. La comparacin de los ejemplos en
C++ y Smalltalk sirve, de paso, como incruenta introduccin a ambos
lenguajes. Lo cierto es que el libro imbuye fcilmente al lector en el object-
thinking, acostumbrndolo a pensar "en objetos", y slo por esto valdra la
pena leerlo. Aparecen, adems, situaciones y decisiones de gran valor
pedaggico. La campaa de comercializacin del libro incluye "El Juego de
los Objetos", con pelotita, silbatos y fichas de cartn, junto con un vdeo en
el que se puede apreciar la vitalidad yanqui de Peter Coad.

LIBROS DE ANLISIS Y DISEO ORIENTADO-A-OBJETOS (OOA &


OOD)

A pesar de la apariencia de complejidad que normalmente se suele aplicar a


estas fases del proceso de desarrollo software, lo cierto es que, como resulta
de mi propia experiencia impartiendo cursos diferenciados de OOP, OOA y
OOD, aunque normalmente se empieza por el lenguaje, cuando se revisan
los mtodos de anlisis y diseo Orientados-a-Objetos, se hace sentir entre
los alumnos una fuerte sensacin de que, comprendidas las bases de estas
disciplinas, se pueden aprovechar mejor los recursos de C++. No estoy
propugnando que el lector escoja necesariamente una de las metologas
presentes en el mercado, sino que examine el sustrato en el que se apoyan y
se apropie de alguna de las tcnicas que en ellas aparecen.

Designing Object-Oriented Software, por Rebecca Wirfs-Brock, Brian


Wilkerson & Lauren Wiener, 1990, Addison-Wesley, 0-13-629825-7, 368
pg.

Este es un texto absolutamente recomendable, sin contrapartida alguna, a


aqullos que buscan iniciarse en las procelosas aguas del Diseo
Orientado-a-Objetos. Esto no significa, empero, que se trate de un libro
elemental: de hecho, la solidez conceptual en la que se apoya convierte al
texto en una muy fructfera fuente y base referencial para profesionales y
equipos con experiencia en Tecnologa de Objetos. Esta obra intenta, en
sntesis, proporcionar un mtodo de OOD independiente de lenguajes y aun
de notaciones especiales. Dado que el autor del presente libro es
especialmente sensible a lo que se conoce como diagramana (esa costosa,
compleja y contraproducente acumulacin de dibujos y conexiones,

C++/OOP: UN ENFOQUE PRCTICO Pgina 144/297


escasamente diferenciados entre s, y que suelen conducir a analista,
diseador, cliente y, en general, a cualquiera que inocentemente los
examine, a un estado de perplejidad cercano al catatnico), la exposicin
que aqu se realiza, capitaneada por Wirfs-Brock, es como una burbuja de
oxgeno en la luna. El grueso del libro trata de lo que otros, en esta misma
materia, dan mayormente por supuesto o examinan muy brevemente: la
identificacin y asignacin de objetos y de sus interrelaciones. Para el
tratamiento de la informacin relacionada con el proceso de OOD se usan las
denominadas fichas CRC (Clase-Responsabilidad-Colaboracin): en cada una
de ellas se significa el nombre de la clase, si es abstracta o no, las
superclases y las subclases de la misma, una sucinta descripcin de su
cometido, las responsabilidades que asume (ese suma de un cierto tipo de
conocimiento que la clase posee y las acciones que puede efectuar), y las
colaboraciones necesarias para cumplimentar cada una de sus
responsabilidades (esto es, la relacin de clases necesarias para llevar a cabo
las tareas o responsabilidades asignadas y para las que la clase no es
autosuficiente). Se trata en sntesis del siguiente proceso: en una primera
etapa exploratoria se produce la identificacin de clases, identificacin de
clases abstractas, identificacin y asignacin de responsabilidades e
identificacin de colaboraciones; en la siguiente etapa, denminada de
anlisis, se modela la construccin de jerarquas (clases en derivacin), se
identifican los contratos (una serie cohesiva de responsabilidades
normalmente requeridas por otras clases) y se asignan a las colaboraciones,
se identifican los subsistemas (abstracciones que permiten un manejo ms
adecuado del sistema global a modelar), se refinan las colaboraciones y se
protocolizan las responsabilidades (esto es, cada una de las responsa-
bilidades se transforma en el prototipo de una funcin). Se obtiene, al final,
algo as como una estructura de clases y sus relaciones vaca de
implementacin. Pero es que la implementacin no es lo importante: la
identificacin de las responsabilidades (por servicios) pblicos de las clases
permitir fragmentar la implementacin de clases de una manera adecuada
para el trabajo en equipo (una clase o serie de clases pueden serle asignadas
a una persona, por ejemplo, y sta nicamente sabr del resto de las clases
que pueden ser accedidas a travs de un protocolo pblico ya bien definido,
independientemente de su implementacin concreta). El seguimiento del
clsico ejemplo del cajero automtico es particularmente revelador sobre el
proceso de diseo expuesto, con abundantes comentarios y la explicacin
detallada de las decisiones tomadas. Este ejemplo, y otros, estn expuestos
en su totalidad en los apndices del libro. Expuesto lo anterior, reitero mi
recomendacin de uso de este libro quizs como el primer libro de OOD que
los principiantes debieran estudiar.

C++/OOP: UN ENFOQUE PRCTICO Pgina 145/297


Object-Oriented Modeling and Design, por James Rumbaugh, Michael
Blaha, William Premerlani, Frederick Eddy & William Lorensen, 1991,
Prentice Hall, 0-13-629841-9, 528 pg.

C++/OOP: UN ENFOQUE PRCTICO Pgina 146/297


En este libro se expone una metodologa de anlisis y diseo orientados a
objetos, que abarca el ciclo completo de desarrollo de software, creada por
el equipo de investigacin de General Electric liderado por James
Rumbaugh. De todas las metodologas propietarias examinadas aqu, sta es
una de las ms completas y, a la vez, la que menos reniega de las bien
conocidas metodologas de anlisis y diseo estructurados en las que
abiertamente se basa. Los autores plantean el traslado del dominio del
problema en el mundo real al campo software a travs del modelado de los
objetos que aparecen en aqul, junto con sus relaciones. Y aunque esto
mismo podra decirse de cualquier otro tcnica de OOA&OOD, el mtodo
aqu expuesto, denominado OMT (Object Modeling Technique: Tcnica de
Modelado de Objetos), se descompone en tres submtodos: el modelado de
objetos, el modelado dinmico y el modelado funcional. Examinemos, por
su inters, aun de forma sucinta, estas tres subtcnicas: en el modelo de
objetos se describen, con una notacin clara y precisa, los atributos y las
relaciones estticas entre los clases a que pertenecen los objetos del
problema y sus relaciones ya modeladas; en el modelo dinmico se
exponen, mediante diagramas de transicin de estados, los aspectos del
sistema relacionados con el control del esquema secuencial y temporal de las
operaciones que afectan a los objetos modelados, usando, para mayor
facilidad en la comprensin del diagrama, la abstraccin de Harel que
permite la anidacin de subsistemas de estados; en el modelo funcional se
exponen, por ltimo, los aspectos del sistema relacionados con las
transformaciones de la representacin interna de los objetos modelados, ya
expresadas estticamente como operaciones en el modelo de objetos, a
travs de los muy conocidos digramas de flujo de datos. Quiz el modelado
funcional resulte el de peor acoplamiento en las bases conceptuales de un
esquema de objetos, pero, aun as, su interrelacin con las dems tcnicas
es modlica. Estos tres modelos se engarzan a travs de lo que se pretende
-o mejor, adivina- sea un esquema no secuencial de fases: anlisis, diseo
del sistema y diseo de objetos. La exposicin final de ejemplos (un
compilador de diagramas de objetos, animacin computerizada y un sistema
de diseo de distribucin elctrica) es un excelente complemento de esta
tcnica. Estamos, en definitiva, ante un excelente texto, aderezado con
comentarios ms o menos acertados sobre bases de datos relacionales y con
extensiones a objetos, lenguajes no orientados-a-objetos, etc. Se aprecia,
por ltimo, una importante caracterstica de cohesividad a lo largo de la
exposicin de la metodologa que le proporciona una solidez conceptual a
que otras son ajenas. La OMT ha ido ganando, desde su publicacin,
adeptos entre los usuarios de OOA&OOD, habindose posicionado, en estas
fechas, como una de los mtodos de uso ms extendido. Se utilice o no esta
tcnica, lo cierto es que el libro es, en todo caso, una valiossima
contribucin a cualquier biblioteca de OT. General Electric ha desarrollado
una herramienta denominada OMTool para distintas plataformas que
computeriza estas tcnicas.

C++/OOP: UN ENFOQUE PRCTICO Pgina 147/297


Object-Oriented Design with Applications, por Grady Booch, 1991,
Benjamin/Cummings, 0-80353-0091-0, 580 pg.

Este libro, muy en la lnea de los dos anteriores, es uno de los ms


considerados por la comunidad C++. Abarca nicamente la etapa de diseo,
no recogiendo las ltimas adiciones a lo que se denomina notacin Booch.
Constituye la perfecta continuacin, con un carcter marcadamente propio,
del texto de Wirfs-Brock. Como la mayora de los textos en OOA y OOD,
empieza por una revisin de los conceptos bsicos de la orientacin a
objetos, tales como clases, objetos, etc., exponiendo distintos mecanismos
para la identificacin de estos dentro del dominio de un problema.
Seguidamente se expone el mtodo de OOD de Booch, junto, cmo no, a su
correspondiente notacin (donde aparecen las famosas nubes, que el lector
posiblemente habr visto en alguna ocasin). Por ltimo se detallan
suficientemente distintos ejemplos aplicativos de la metodologa expuesta,
implementados en CLOS, C++, Smalltalk, Ada y Object Pascal. Un apndice
sobre lenguajes de programacin orientados y basados en objetos, adems
de 46 pginas repletas de bibliografa completan el volumen. Nos encontra-
mos, pues, ante uno de esos raros textos densos en contenido y, a la vez, de
brillante practicidad y fuertes capacidades referenciales. Piense el lector que
uno de los "reproches" que se le imputan a Booch es la "excesiva riqueza" de
su notacin. Mi consejo? Este debe ser el segundo o, a lo sumo, tercer libro
de diseo que adquieran. Existe una herramienta denominada Rose, de la
compaa Rational (la misma a que pertenece Grady Booch), que soporta
esta metodologa.

C++/OOP: UN ENFOQUE PRCTICO Pgina 148/297


Object-Oriented Analysis, 2nd Edition, por Peter Coad & Edward
Yourdon, 1991, Yourdon Press/Prentice Hall, 233 pg.

Nos encontramos ante la segunda edicin de uno de los primeros textos


aparecidos en el mercado sobre OOA. Esta premura editorial consigui que
las ideas propuestas por Coad y Yourdon se extendieran con gran rapidez,
siendo as que este mtodo ha permanecido durante mucho tiempo como
uno de los ms ampliamente difundidos. El libro es uno de los ms breves
de los comentados en este anexo y est escrito en un lenguaje coloquial de
muy fcil lectura. Los conceptos bsicos del paradigma de objetos se
exponen con la ayuda de definiciones de diccionarios y enciclopedias. La
graficacin, una de las primeras y, por tanto, de las ms rudimentarias en
este campo, bsicamente expone el resultado grfico de la herramienta
comercializada por los autores (OOATool). Las secciones de introduccin y
comparacin de distintas metodologas de anlisis anteriores al OOA
(Yourdon-de Marco, Jackson, etc.) son agradablemente sucintas y claras. Lo
nico que se puede reprochar es la falta de un formalismo metodolgico
prctico que permita al lector hacer uso de lo aprendido. De hecho el lector
se queda al final del texto con una cierta sensacin de borrachera de objetos,
generada en buena medida por el nfasis y la excitacin con que los mismos
autores tratan a la OT, pero sin direccionamiento prctico claro en el que
sostener sus primeros pasos en este campo. El libro es, pues, perfectamente
aconsejable como texto introductorio a OOA.

Object-Oriented Design, por Peter Coad & Edward Yourdon, 1991,


Prentice Hall, 0-13-630070-7.

Estamos ante una clara continuacin del anterior libro de los autores sobre
OOA, que usa de una extensin apropiada a OOD de la herramienta de
anlisis OOATool de los autores. Tras una leve introduccin (pues el texto,
como el anterior, es singularmente corto), se repasa la metodologa de
Anlisis orientado-a-objetos de Peter Coad: un modelo multicapa (sujeto,
clase-objeto, structura, atributos y servicios) y multicomponente (dominio
del problema, interaccin humana, gestin de tareas y gestin de datos),
con una notacin especfica de aplicacin. Los autores intentan, con cierto
xito, integrar de forma incruenta las tcnicas de OOD con las del proceso
de OOA, para pasar despus a exposiciones sobre sectores de parcial
inters, como el de las herramientas CASE o los distintos lenguajes de
programacin. El texto, con las mismas salvedades de la obra anterior, es
totalmente recomendable como introduccin no reglada al campo del OOD.

C++/OOP: UN ENFOQUE PRCTICO Pgina 149/297


Object-Oriented Analysis: Modeling the World in Data, por Sally
Shlaer & Stephen J. Mellor, 1988, Yourdon Press/Prentice Hall, 144 pg.

Tenemos aqu a uno de los pocos textos serios focalizados en el rea de


anlisis orientado-a-objetos. Con un corto nmero de pginas, la obra
comienza con una simptica introduccin a los tpicos conceptos bsicos,
pasando a poco a clasificar los objetos en: tangibles, roles, interacciones,
incidentes y especificaciones (he de reconocer que esta fragmentacin
conceptual yo siempre la he asumido como de identificacin de clases).
Seguidamente expone un modelo de control textual de especificaciones de
clases como soporte de la tcnica desarrollada por los autores y denominada
modelado de informacin. No es ste un libro que proporcione un bagaje
semejante al de Wirfs-Brock, ni la tcnica en l descrita es particularmente
fcil de aplicar, pero, con todo, el texto ofrece detalles muy interesantes para
el estudioso, as como ejemplos altamente intuitivos y de ilustraciones
autoexplicativas.

Object Lifecycles: Modeling the World in States, por Sally Shlaer &
Stephen J. Mellor, 1991, Prentice Hall, 0-13-629940-7.

Esta obra estudia, tras un breve repaso comprehensivo de la anterior obra


de los autores, el comportamiento dinmico de los sistemas de objetos.
stos se asimilan a mquinas de estados (as como las clases a modelos de
estados), de tal forma que el ciclo de vida de un objeto puede modelarse
como un conjunto de estados, de eventos, de reglas de transicin y de
acciones. Seguidamente se muestra el desarrollo de las relaciones entre
objetos afectadas por el tiempo, para pasar despus a la exposicin de los
mtodos de modelado de secuenciaciones de eventos y terminar con una
extensin de los digramas de flujos de datos denominada ADFD y referida
a los datos asociados a acciones de los objetos. Se exponen diversas
posibles aplicaciones de la tcnica de la obra y se muestran algunas lneas de
migracin a esta metodologa desde el enfoque estructurado. En definitiva
nos encontramos ante el perfecto compaero de la anterior obra.

Object-Oriented Systems Analysis: A Model-Driven Approach, por


David W. Embley, Barry D. Kurtz & Scott N. Woodfield, 1992, Prentice Hall,
0-13-629973-3, 302 pg.

He aqu, a mi entender, una de las aportaciones ms significativas realizadas


en los ltimos tiempos al rea del anlisis orientado-a-objetos. Los autores
proveen, a ms de una definicin formal de su mtodo de anlisis (OSA:
Object-Oriented Systems Analysis) basado en lo que llaman ORM (Object-
Relationship-Model), de la que carece el resto, una nueva y rica notacin que
se aplica con exactitud al nuevo enfoque de aproximacin al OOA:
relaciones, estados y modelos de interaccin de objetos. Si bien los modelos
de relacin y de estados son fcilmente asimilables (salvadas ciertas

C++/OOP: UN ENFOQUE PRCTICO Pgina 150/297


distancias) desde otras metodologas (diagramas entidad-relacin, etc.), el
modelo de interaccin reemplaza al clsico diagrama de flujo de datos. El
tratamiento dado, de cualquier forma, a las relaciones es particularmente
rico y revelador: stas aparecen ya como objetos reales, y la notacin
cualificadora de los conjuntos de relaciones resulta increblemente clara, en
perfecto desarrollo de aproximaciones ms tradicionales como la de
Rumbaugh (OMT). La obra contiene una gran cantidad de ejemplos y
cuestiones (existe un libro adicional con la respuesta a los ejercicios
propuestos) sencillamente perfectos. El captulo introductorio es, por otro
lado, suficientemente explicativo, y el ejemplo del trayecto en la ciudad es
realmente bueno. Bien: su biblioteca quedara incompleta sin esta obra. As
de simple.

Object Oriented Program Design with Examples in C++, por Mark


Mullin, 1990, Addison-Wesley, 0-201-51722-1, 303 pg.

Este texto ha llegado a ser muy popular entre un cierto sector de la


comunidad C++, principalmente entre los desarrolladores provenientes de C
y de esquemas de diseo estructurado, debido quizs a la inmediata
practicidad de los conceptos que, sin esquematizacin rigurosa, se van
proponiendo: es como si se diseara en C++. El autor asume un ejemplo (la
creacin de una base de datos corporativa para Bancroft Trading Company)
y en sucesivos captulos va refinndo su diseo. La mayor ventaja para
algunos de este libro (su proximidad a la vida real) es, sin embargo, tambin
su principal defecto. Hay que pensar que en un esquema serio de OOD, por
ejemplo, el diseo de bases de objetos (por bases de datos orientadas a
objetos, o simplemente de datos) no existe. Efectivamente: los modernos
gestores de bases de objetos procuran, mediante la no diferenciacin entre
medios de almacenamiento primario y secundario, una suerte de memoria
virtual infinita para nuestros objetos: no hay que preocuparse de extensiones
del lenguajes, preprocesadores o procedimientos de archivo y recuperacin
de objetos, as como tampoco de convertir los punteros en identificadores, o
de descomponer los objetos en tablas. En realidad, en la mayora de las
implementaciones en las que aparece un OODBMS, los objetos son
persistentes por defecto, a menos que expresamente el desarrollador indique
lo contrario. De acuerdo, perfecto, pero a qu viene esta perorata? Bueno,
si no hay diseo que hacer de bases de datos, el libro entero de Mullin
queda un tanto desenfocado, pues la importancia habra de traspasarse a la
identificacin de clases y relaciones, algo que en el texto se relega a un
segundo plano. Planteada esta observacin, por lo dems el libro es
recomendable como primer estadio de paso en la codificacin C++ basada
en la conceptualizacin de objetos, sin demasiadas pretenciones adicionales.

C++/OOP: UN ENFOQUE PRCTICO Pgina 151/297


A Complete Object-Oriented Design Example, por Joseph E.
Richardson, Ronald C. Schultz & Edward V. Berard, 1992, Berard Software
Engineering Inc, 1-881974-01-4, 350 pg.

Se suele echar de menos, al introducirse en el terreno del OOD, la


disponibilidad de un ejemplo completo, en todas su fases, que rellene esas
carencias, obviadas por una cuestin esencial de espacio en otros textos, y
que colocan al novicio en disyuntivas normalmente difciles de superar. El
presente texto intenta cubrir tan urgente necesidad. El lector fcilmente
podr apreciar que en el libro se explicitan, con toda clase de detalles, las
fases consideradas ms farragosas y de trabajo ms tedioso, normalmente
sustanciadas en listas selectivas: listas y ms listas. Se usan, despus,
diagramas de transicin de estados, redes de Petri y diagramas de Booch
para resolver el problema de diseo de una utilidad de concordancias, tpica
de un procesador de textos. Finalmente se detalla la implementacin
completa de la solucin encontrada, tanto en Smalltalk como en C++. En el
apndice se detallan distintas especificaciones para terminar con una
exposicin parcial de las diapositivas empleadas por la firma de ingeniera de
software, editora del libro, en sus cursos y trainings de OOD.

C++/OOP: UN ENFOQUE PRCTICO Pgina 152/297


OOA & OOD:
A-3
UNA APROXIMACIN
CRTICA

El propsito de este anexo es mostrar, en primer lugar, el sustrato


conceptual en que se basan los sistemas software orientados-a-objetos y
que, a la vez, anima las variadas metodologas que cubren las distintas fases
del desarrollo de software. Seguidamente se practicar una sucinta
aproximacin crtica a parte de las tcnicas que pueblan los distintos
mtodos en las reas de anlisis y diseo orientado-a-objetos, con un
especial nfasis en el campo del diseo, para terminar con una exploracin
de los distintos textos y herramientas comerciales que permiten la aplicacin
de tales metodologas. El tono del texto pretende ser decididamente
globalizador, evitando, en lo posible, la introduccin de matizaciones
conceptuales o terminolgicas que aumenten la aparente confusin formal
en las reas citadas.

SOFTWARE ORIENTADO-A-OBJETOS

El paradigma de la orientacin-a-objetos filtra, matiza y modela


convenientemente cada una de las fases del proceso de mapeo de un
determinado problema real a un sistema software. Podramos decir, pues,
que el sistema software resultante, producto de la sistemtica aplicacin de
criterios y fundamentos bien determinados y diferenciados, ineludiblemente
habr de constituirse en un Sistema Software Orientado-a-Objetos (OOSS:
Object-Oriented Software System). Ahora bien, si se piensa que las fases del
proceso de desarrollo son las bases en que se apoya el OOSS, verdadero fin
de aqul, debemos preguntarnos: Realmente, qu se quiere construir?
Qu es un OOSS? O mejor, qu caracteriza de forma general a un OOSS?

Pero, estamos planteando la pregunta apropiada? Sin duda: una respuesta


acertada restringir y clarificar los caminos de acceso al fin propuesto. De
hecho, otro tipo de pregunta que cuestionara nicamente las diferencias con
relacin a los SSS's (Structured Software Systems) nos habra de conducir a

C++/OOP: UN ENFOQUE PRCTICO Pgina 153/297


la equivocada va de considerar la Orientacin-a-Objetos como una mera
adenda al paradigma estructurado. De acuerdo con esto, lo primero sera
intentar establecer las principios bsicos caracterizadores de un OOSS, y,
para esto, debemos examinar lo que a este respecto opinan distintos
autores. En [Hende90] aparece un cuadro de especial inters, por cuanto
que evidencia la disparidad de criterio, an no consensuada en la actualidad,
sobre lo que en esencia define a un OOSS: ocultacin de informacin,
encapsulacin, objetos, clasificacin, clases, abstraccin, herencia,
polimorfismo, ligadura dinmica, persistencia y composicin. Cabe notar, no
obstante, la fuerte concordancia en la "herencia" como una caracterstica
bsica, aunque es mi opinin -coincidente con Winblad et al, Bloy et al y
Henderson-Seller- que la herencia es ms un mecanismo bsico que un
concepto bsico. Pensemos, por ejemplo, que en C++ la derivacin (por
herencia) es el mecanismo que conviene al lenguaje caractersticas
polimrficas. Hay que ponderar, tambin, que existen otros mecanismos,
como el de delegacin, sustitutivos de la herencia. De esta manera, Hender-
son-Sellers establece lo que denomina "Tringulo de la Orientacin-a-
-Objetos", en el que se establecen los pilares bsicos de los OOSS:
abstraccin, encapsulacin y polimorfismo. Naturalmente, esta figuracin es
resultado de un siempre arriesgado truncamiento de opciones, por lo que es
tan discutible como las aproximaciones en que se basa.

Realmente es difcil encontrar, en la cada vez ms profusa -y confusa-


relacin bibliogrfica de orientacin-a-objetos, una rigurosa caracterizacin
extensiva de los OOSS. Las caractersticas notadas en el prrafo anterior
parecen, de hecho, corolarios de conceptualizaciones de ms bajo nivel en la
comprensin de tales sistemas software. Una de las pocas revisiones
formales de los OOSS's la encontramos, sin embargo, en [Meyer88], que
afirma, tras establecer la modularidad como objetivo esencial en los
sistemas software, que sta habr de estar animada y regida por cinco
criterios y seis principios esenciales. Por su especial inters, procederemos a
revisarlos con ms detalle, pues mediante el examen de su cumplimiento en
un determinado sistema software podr determinarse, con la vaguedad
formal propia de este paradigma, el grado de orientacin-a-objetos de ste.
Meyer no pretender describir una metodologa, pero, como se pondera en
[Rumba91], ofrece muy buenas tcnicas de buen diseo.

C++/OOP: UN ENFOQUE PRCTICO Pgina 154/297


CINCO CRITERIOS SOBRE MODULARIDAD EN OOSS

Criterio de Descomponibilidad Modular: Un OOSS cumplir este criterio si


facilita la fragmentacin de un problema en varios subproblemas o, en otro
nivel conceptual, favorece que los mdulos de que se compone sean
particionables en submdulos, y estos a su vez en otros, hasta llegar a un
nivel que permita aprehender y abordar su tratamiento individual.
Naturalmente la tpica estructuracin top-down cumple la condicin descrita,
aunque, en realidad, hay que considerarla como una muy particular (e
inmediatamente eficiente) interpretacin del criterio, como un modelo en
base a diagramas de Venn rpidamente evidencia: la libertad de
descomposicin se troca aqu en forzosa estructuracin arbrea.

Criterio de Componibilidad Modular: Se trata aqu del grado de facilidad con


que pueden combinarse de forma flexible (aunque no siempre con total
independencia) los mdulos de que se compone un OOSS. Este criterio est
directamente relacionado con la posibilidad de reutilizacin de componentes
para la creacin de nuevos sistemas software en diferentes entornos y
escenarios. De hecho, en justa correspondencia, la reutilizacin ha sido
definida como "la capacidad efectiva de incorporar objectos creados para un
sistema software dentro de un sistema software diferente" ([Wasse91]). Hay
que tener en cuenta, empero, que el criterio de descomponibilidad no
conduce necesariamente a ste, pues la mera descomposicin suele abocar
-como ocurre en el enfoque top-down- a mdulos no reutilizables
(cuestionmonos, si no, cmo podramos "recomponer" a una persona viva
anteriormente descuartizada: quiz con un mdulo Shelley?). La idea que,
en definitiva, subyace en las ideas expuestas es la del uso de distintos
repositorios de mdulos (quizs modelados como OODBMS's) que permitan
la construccin independiente de sistemas software.

Criterio de Comprensibilidad Modular: De poco sirve una estructura o


configuracin modular si sta no puede ser parcialmente asignada o
comprendida por un observador externo. Si atendemos, por otro lado, a la
consideracin de G.I.Miller de que el lmite humano medio para el
procesamiento de informacin, en el marco de la memoria inmediata, se
situa en tres distintos conjuntos conteniendo un mximo de tres tems cada
uno, la necesidad de poder extraer de un OOSS un pequeo subsistema
comprensible para el lector se torna an ms perentoria. El presente criterio
ilustra, pues, la necesidad de que los mdulos en un OOSS sean
autoexplicativos o, a lo sumo, extiendan su comprensibilidad a alguno de los
mdulos adyacentes.

Criterio de Continuidad Modular: De forma intuitivamente parecida a como


ocurre con la continuidad de funciones y=f(x), donde informalmente puede
decirse que un pequeo cambio en x produce un necesariamente pequeo
cambio en y, subyace bajo este criterio una idea que se ha totemizado,
justamente, en los manuales de estilo de OOP: "pequeos cambios han de
originar pequeas repercusiones". Un ejemplo prctico de este criterio lo

C++/OOP: UN ENFOQUE PRCTICO Pgina 155/297


constituyen las facilidades polimrficas de la ligadura dinmica, que
permiten la incorporacin de un nuevo modulo a un OOSS sin apenas
cambios que afecten a los dems: as en C++, mediante el mecanismo de
funciones virtuales, pueden aadirse al sistema objetos de clases nuevas,
sometidas a una relacin jerrquica de derivacin pre-establecida, sin
modificar en absoluto el esquema de mensajes y relaciones de tipo entre
clases.

Criterio de Proteccin Modular: Se pretende que la propagacin en tiempo


de ejecucin de un error en un mdulo resulte local a ste o, como mximo,
se extienda a algunos de los mdulos adyacentes.

SEIS PRINCIPIOS ESENCIALES EN OOSS's

Unidades Modulares Lingsticas: De acuerdo con los criterios anteriormente


establecidos, es imposible pensar en mdulos a la vez descomponibles,
componibles, comprensibles, continuos y protegidos contra la propagacin
de errores si stos no representan entidades conceptuales con lmites
sintcticos bien definidos. La encapsulacin de informacin en mdulos sin
tales fronteras semnticas deviene desafortunadamente frgil, apropiada
quizs para un escenario especfico, pero de difcil o imposible extrapolacin
a otros. La aplicacin de este principio se ha convertido, difundida en la base
de distintos mtodos de OOA & OOD, en uno de los criterios evaluadores de
la idoneidad de los mdulos identificados en las distintas fases del ciclo de
vida de un OOSS: "Si a un candidato a mdulo, clase o subsistema no se le
puede aplicar una unidad lingustica del lenguaje en que estemos operando,
es momento de reconsiderar las decisiones que condujeron a su eleccin".
Como quiera que la obra de Meyer est fuertemente orientada a la
imposicin de un lenguaje como expresin visual del diseo, llega a
concluir, a diferencia del posicionamiento general de autores de otras
metodologas de orientacin grfica, que la implementacin de estos diseos
no podr ser abordada por lenguajes que carezcan de tales facilidades
modulares sintcticas.

Pocos Interfaces: Un mdulo debe comunicarse con tan pocos otros


mdulos como sea posible. En trminos de Wirfs-Brock, la "inteligencia" de
un sistema debe distribuirse entre los mdulos de forma local a stos. Lo
que se pretende, al fin, es liberar al propio sistema bien de mdulos
profusamente interconectados entre s (vulnerando la totalidad de los
criterios expuestos) bien de mdulos que encierren la total "inteligencia" del
mismo y que, en tal razn, deban comunicarse, en claro esquema
centralista, con la mayora de los dems.

Interfaces Pequeos: Un mdulo debe intercambiar con los otros mdulos


tan poca informacin como sea posible. Naturalmente la restriccin de
opciones, habitualmente concretada en la limitacin de los mensajes a que
un mdulo puede responder, ayuda sobremanera al cumplimiento de este

C++/OOP: UN ENFOQUE PRCTICO Pgina 156/297


principio. De hecho, su ms extendida violacin se sustancia en las
interminables listas de argumentos entre mdulos, normalmente debidas al
malentendido uso de libreras, expresamente contrarias a los criterios de
continuidad y proteccin modular.

Interfaces Explcitos: Se enfatiza aqu la necesidad de que la comunicacin


entre mdulos sea inmediatamente evidente al observador externo. Por
qu? Bueno, es ciertamente difcil reutilizar, trasladando de un escenario a
otro, mdulos que mantienen relaciones sutiles o subrepticias con otros
mdulos o subsistemas, pues stas no habrn sido tenidas en cuenta y bien
sern cercenadas de forma peligrosa, bien originarn indeseables efectos
laterales difciles de depurar. De alguna manera el interfaz del mdulo es el
equivalente a la ficha policial de ste: no se desean sorpresas. Meyer
sintetiza con acertado grafismo la aplicacin de ste y los dos anteriores
principios: es como si se instaurara la dictadura en el universo de los
mdulos, pues a estos no se les deja reunirse en grupos numerosos, se les
obliga a cruzar tan slo algunas palabras entre ellos y, adems, se les fuerza
a hablar gritando. Quiz una aproximacin similarmente didctica consista
en considerar a los mdulos como psicpatas en libertad vigilada.

Ocultacin de la Informacin: como directa consecuencia de la


encapsulacin, cada mdulo esconde sus datos de otros mdulos de manera
que stos slo podrn ser accedidos a travs de los mtodos del propio
mdulo. Algunos autores, como Meilir Page-Jones, prefieren hablar ms
bien de "ocultacin de la implementacin", mientras que otros, como Booch,
directamente asimilan la intercambiabilidad de ambos conceptos. El presente
principio enfatiza la necesidad de que la informacin local a un mdulo
posea un nivel de proteccin de acceso (privacidad) por defecto, declarando
como perteneciente al interfaz de acceso pblico nicamente la informacin
estrictamente necesaria. Lo que aqu se enfatiza es, entre otras cosas, la clara
separacin entre interfaz e implementacin, a la par que se refuerza la idea
de acceso cuidadoso y selectivo a una informacin que de otra manera
pudiera malusarse o corromperse. Una buena figuracin didctica la
constituira aqu la del tonel de vino, convenientemente cerrado para
asegurar su conservacin y nicamente accesible a travs de un pequeo
interfaz: el grifo.

Mdulos Abierto-Cerrados: Segn el enfoque clsico de diseo, un mdulo


cerrado es aqul que, superadas las etapas de testeo y validacin de
estabilidad, queda listo para su uso, a travs de su interfaz, por otros
mdulos. Un mdulo abierto sigue, en contrapartida, sujeto a posibles
extensiones. Qu ocurre, empero, cuando hay que modificar un mdulo
cerrado? Pues que ste debe ser abierto, modificado y sometido de nuevo al
proceso de testeo y estabilizacin de uso, amn de que se genera la
necesidad de actualizar los sistemas clientes del antiguo mdulo. La solucin
de un OOSS es la siguiente: los mdulos han de ser, a la vez, abiertos y
cerrados. Si se detecta un problema en un mdulo, o si ste debe ser
modificado para cambiar algn aspecto de su comportamiento, en vez de

C++/OOP: UN ENFOQUE PRCTICO Pgina 157/297


abrir y acceder directamente al mdulo dado, de alguna manera ste se
"clona" incorporndose, como parte fsica o conceptual, en otro nuevo,
posiblemente superconjunto de aqul, de forma que es en este nuevo
mdulo donde se realizan los cambios adecuados. No hay necesidad, pues,
de modificar los mdulos existentes, de manera que no se pueden extender
al sistema los posibles errores posiblemente cometidos en este proceso.
Naturalmente se inserta uno o ms nuevos mdulos en el sistema, pero de
acuerdo con el criterio de continuidad -y haciendo uso, normalmente, de la
cualidad de polimorfismo que caracteriza a un OOSS-, esta pequea adicin
originar un cambio pequeo o nulo en el sistema total. Precisamente la
programacin visual, bsica y tradicionalmente sustentada en la interaccin
de mdulos cerrados de utilizacin inmediata como "cajas negras", al aplicar
extensiones de orientacin-a-objetos empieza a posibilitar, en aplicacin de
las obvias ventajas de este principio, la "derivacin visual" modular.

BOTTOM-UP VERSUS TOP-DOWN

El enfoque top-down se fundamenta en el refinamiento gradual de lo que se


considera funcin principal o abstracta del sistema. De aqu fcilmente se
infiere que, segn este enfoque, todo sistema ha de poder ser sintetizado en
una funcin "top", pero lo cierto es que los sistemas reales no poseen tal
abstraccin "top". El mtodo top-down simplemente pregunta: Qu debe
hacer el sistema?, apoyndose as en la parte ms voltil del mismo, el
interfaz externo, y estableciendo prematuramente la secuenciacin de las
acciones a realizar. Una aproximacin metodolgica esencialmente ms
apropiada sera la basada en la sustancia del sistema: los datos. Pero no se
trata aqu de una mera traslacin del foco del esfuerzo de proceso de
software desde las funciones a los datos, obteniendo poco ms de una nueva
problemtica de caractersticas opuestas: en los OOSS's, por el contrario, las
funciones encuentran, segn Meyer, su exacto emplazamiento en los ADT's.

Lo ms importante de la nueva orientacin se resume en la premisa:


primero hay que mirar a los datos, obviando el propsito efectivo del
sistema, de tal manera que cuanto ms tarde modelemos "lo que hace" ste,
mejor. Se introduce as una esquematizacin denominada de "shopping list",
donde se describen las operaciones susceptibles de ser aplicadas a cada
mdulo liberadas de las restricciones de orden. La prdida, pues, de la
prevalencia inicial de la secuenciacin conlleva una aproximacin al punto de
vista del sistema a ser modelado, de manera que se generan sistemas
robustos, no dependientes de la impulsin de las relaciones temporales, que
pueden cambiar sin afectar a la estructura de aquellos. Se dice, as, que la
construccin de un OOSS se realiza de abajo hacia arriba (bottom-up), en
clara inversin del tradicional top-down. La realidad, sin embargo, es que la
flexibilidad propia de los procesos de OOA y OOD origina una frecuente
mixtura iterativa entre ambas tcnicas, aunque, eso s, supeditada a lo que
se denomina enfoque "model-driven" o "responsability-driven".

C++/OOP: UN ENFOQUE PRCTICO Pgina 158/297


EJEMPLOS DE CONCEPTUALIZACIN OBJETUAL

"Un objeto tiene un conjunto de operaciones y un estado que memoriza el


efecto de las operaciones" (J. Bzivin)

"Un objeto es algo que existe en el tiempo y en el espacio y que puede ser
afectado por la actividad de otros objetos" (Grady Booch)

"Un objeto es un concepto, abstraccin o cosa con lmites bien definidos y


significado para el problema abordado" (James Rumbaugh et al.)

"Un objeto posee estado, comportamiento e identidad; la estructura y


comportamiento de objetos similares estn definidas en su clase comn"
(Grady Booch)

"Un objeto posee operaciones que definen su comportamiento y variables


que definen su estado entre llamadas a las operaciones" (P. Wegner)

CICLO DE VIDA DE UN OOSS

El ciclo de vida de desarrollo software convencional est fuertemente


focalizado en fases, constituyendo lo que se denomina ciclo en cascada
(waterfall), de forma que cada fase utiliza diferentes tcnicas y produce
distintos resultados agrupados en su conclusin, que sern utilizados por
otras fases subsiguientes. El ciclo de vida orientado-a-objetos sigue, en
contrapartida, un esquema paralelo-recursivo en el que las tradicionales
fases se solapan en el tiempo, debido a un desplazamiento de la focalizacin
hacia los objetos, produciendo resultados discretos a lo largo de todo el
ciclo, en un proceso involutivo de continuo refinamiento. Realmente, desde
un punto de vista de gestin de proyectos, el nfasis en perodos temporales
s recae secuencialmente en fases concretas, pues, por ejemplo, el esfuerzo
inicial se dirige sobremanera al anlisis de requerimientos, decreciendo
paulatinamente conforme el proyecto avanza. As, por ejemplo, [Booch91]
establece los siguientes porcentajes: Anlisis (25%), Diseo (38%),
Codificacin (13%), Chequeo (19%) e Integracin (7%).

La naturaleza recursiva del ciclo de vida de un OOSS queda patente, por


ejemplo, en la sntesis de [Berar92]: "Analiza un poco, disea un poco,
implementa un poco, chequea un poco", as como en el modelo de fuente de
[Hende90], de grafismo autoexplicativo. James Rumbaugh explica, de forma
ingeniosa, que un desarrollo realstico de software es ms como una piscina
que como una cascada, esencialmente con agua en comn.

C++/OOP: UN ENFOQUE PRCTICO Pgina 159/297


TRANSICIN DEL OOA AL DISEO ORIENTADO-A-OBJETOS

James J.Odell ha afirmado que el OOA "no debiera modelar la realidad, sino
ms bien la forma en que la realidad es comprendida por la gente". Pero,
qu se entiende aqu por "modelo"? Segn Michael Blaha, "una abstraccin
de algo con el propsito de comprenderlo antes de construirlo. Los modelos
incluyen slo aquellos aspectos relevantes a la solucin de un problema; los
detalles extraos son ignorados".

En la etapa de anlisis se identifican entidades conceptuales, con contornos


bien definidos, correspondientes a la abstraccin de singularidades del
escenario concreto por el que se matiza nuestro conocimiento del mundo
real. En la etapa de diseo, seguidamente, tales entidades abstractas se
trocan -directamente, en correspondencia biunvoca- en clases y objetos con
las mismas estructura y organizacin. Se produce as, como vemos, un
mapeo uno-a-uno entre componentes de las fases de anlisis y diseo
orientado-a-objetos, que algunos autores reclaman debiera ser extensivo a la
etapa de implementacin tambin (usando posiblemente de la misma
notacin y esquemas), y que aligera notablemente el tradicional "gap" entre
SA/SD (Structured Analysis/Structured Design), suavizando hasta tal punto
la transicin entre OOA y OOD que se torna difcil establecer los lndes entre
ambas fases. Naturalmente esto se debe al alto nivel de integridad
conceptual y consistencia procurado por las metodologas de orientacin-
-a-objetos.

En [Wasse91a] se establece que un autntico OOA habra de identificar un


conjunto de clases y las relaciones entre stas, incluyendo las de uso y
herencia, modelando as el conocimiento del sistema de una forma completa
e inambigua, permitiendo que ese modelo pueda ser continuamente refinado
y comunicado a otros. El hecho de que la fase de OOA no tenga por qu
identificar todos los objetos de un sistema (pues durante el proceso de OOD
sern identificados algunos objetos adicionales, a la vez que sern
rechazadas algunas de las entidades halladas en el proceso de anlisis),
refuerza la naturaleza involutiva del ciclo de vida de un OOSS.

De cualquier forma, y a pesar del borroso linde entre ambas fases, es


conveniente recordar que el Anlisis es una actividad focalizada-en-el-proble-
ma (el qu), mientras que el Diseo est focalizado-en-la-solucin (el cmo),
de manera que es posible que en alguna ocasin la mera traslacin directa a
la fase de diseo de los resultados del anlisis constituya una error de
imprecisin, aunque sea cierto, en cualquier caso, que el sustrato conceptual
trasladado ha de ser bsicamente correcto.

C++/OOP: UN ENFOQUE PRCTICO Pgina 160/297


DISEO ORIENTADO-A-OBJETOS

Segn afirma Meyer, el OOD representa "la construccin de sistemas


software como colecciones estructuradas de implementaciones de Tipos de
Datos Abstractos" [Meyer88]. A fin de intentar extraer el sustrato comn
bajo los mtodos de OOD, examinaremos tres de ellos:

I.. Mtodo de GRADY BOOCH


A.. Modelos de OOD
Error! Argumento de modificador desconocido...
Lgicos
a.. Estructuras de clases
b.. Estructuras de objetos
Error! Argumento de modificador desconocido... Fsicos
a.. Arquitecturas modulares
b.. Arquitecturas de procesos
B.. Proceso de OOD
Error! Argumento de modificador desconocido...
Identificacin de clases objetos a un nivel dado
Error! Argumento de modificador desconocido...
Identificacin de relaciones entre clases y objetos
Error! Argumento de modificador desconocido...
Implementacin de clases y objetos
C.. Diagramas de clases
Error! Argumento de modificador desconocido...
Estructuras
Error! Argumento de modificador desconocido...
Especificaciones
Error! Argumento de modificador desconocido...
Relaciones

II.. Mtodo de BERARD


A.. Establecimiento del problema
B.. Identificacin de objetos candidatos
Error! Argumento de modificador desconocido...
Identificacin de objetos de inters
Error! Argumento de modificador desconocido...
Asociacin de atributos con objetos de inters
C.. Identificacin de operaciones (server/client) de objetos
D.. Aplicacin recursiva de OOD
E.. Seleccin, creacin y verificacin de objetos
F.. Decisines de implementacin de objetos
G.. Creacin de modelos grficos orientados-a-objetos
H.. Establecimiento de los interfaces de los objetos
I.. Implementacin de los objetos

III.. Mtodo de JAMES RUMBAUGH et al.


A.. Modelado de objetos

C++/OOP: UN ENFOQUE PRCTICO Pgina 161/297


Error! Argumento de modificador desconocido...
Objetos y clases
Error! Argumento de modificador desconocido... Enlaces
y asociaciones
Error! Argumento de modificador desconocido...
Generalizacin y Herencia
Error! Argumento de modificador desconocido... Grupos
Error! Argumento de modificador desconocido... Clases
abstractas
Error! Argumento de modificador desconocido... Otros
B.. Modelado Dinmico
Error! Argumento de modificador desconocido...
Eventos y estados
Error! Argumento de modificador desconocido...
Operaciones
Error! Argumento de modificador desconocido...
Diagramas de estados anidados
Error! Argumento de modificador desconocido...
Concurrencia
C.. Modelado Funcional
Error! Argumento de modificador desconocido...
Modelos funcionales
Error! Argumento de modificador desconocido...
Diagramas de flujo de datos
Error! Argumento de modificador desconocido...
Operaciones

Como bien se puede notar en las fases expuestas, una demasiado simple
esquematizacin intuitiva de todas ellas podra resultar en la siguiente
secuencia involutiva: descripcin textual del problema, identificacin y
descripcin de objetos, establecimiento y descripcin de relaciones y
comunalidades entre objetos, y descripcin de los procesos temporales de
cambios de estados de objetos.

C++/OOP: UN ENFOQUE PRCTICO Pgina 162/297


CLASIFICACIN DE LOS MTODOS DE OOD

Dado que no existe en la actualidad un nico mtodo estndar de OOD, y ni


siquiera hay convenio en la aceptacin formal de los distintos conceptos
bsicos que conforman el paradigma de orientacin a objetos, hablar de
esquemas clasificatorios o evaluadores de las tcnicas de OOD supone entrar
en un segmento metodolgico de escasa madurez formal, de manera que
perfectamente cabra evaluar los mtodos de evaluacin a considerar, y as
sucesivamente. En [Arnol91], por ejemplo, se decide la aplicacin de
criterios (soportes conceptuales, herencia, visibilidad, tiempo de vida,
concurrencia, comunicacin, clases de modelos, notaciones, contexto de
desarrollo, cobertura de ciclo de vida, propiedades del proceso, recursos,
accesibilidad y aplicabilidad) para finalmente concluir, obviando los tpicos
cuadros de evaluaciones parciales, con apreciaciones intuitivas de carcter
genrico del tipo "el mtodo Wirfs-Brock soporta totalmente los conceptos
de orientacin-a-objetos. El proceso es exploratorio e informal y resulta ms
apropiado para el desarrollador individual que para grandes equipos".
[Berar92a], por otro lado, desarrolla una secuenciacin evaluadora
intentando evitar los defectos conceptuales en que afirma suelen recaer este
tipo de trabajos, aunque resulta ciertamente chocante, a pesar de la seriedad
del informe, la prevalencia que se le da al mtodo de Booch y, sobre todo, al
de Berard, autores del trabajo, basado grandemente en aqul. Las conclusio-
nes de Berard sobre los distintos mtodos se agrupan, sin embargo, en
razn de las necesidades y posibles requerimientos de los potenciales
usuarios de los mismos, por lo que puede resultar de utilidad prctica a la
hora de afrontar una primera decisin electiva. La abundancia de estndares
fuerza a que los informes comparativos confluyan en recomendaciones de
uso, frecuentemente influidas por el bagaje subjetivo de los autores de los
mismos.

Una clasificacin genrica de los mtodos de OOD sera cualificarlos como


unarios o ternarios, dependiendo de si se basan en un enfoque globalizador
desde el punto de vista de la orientacin-a-objetos o ms bien constituyen
una adaptacin de las tradicionales fases del proceso de SA/SD. As,
verbigracia, seran unarios los mtodos de Coad/Yourdon y de Wirfs-Brock,
mientras que resultaran ternarios los de Rumbaugh y Shlaer & Mellor. De
esta manera los mtodos ternarios aparecen, en un primer esbozo,
especialmente indicados para procesos corporativos de transicin al nuevo
paradigma de objetos, mientras que los unarios resultan adecuados para los
desarrolladores individuales y los procesos de asuncin integral de la nueva
orientacin modular.

Un tercer estadio clasificatorio lo constituiran los mtodos de OOA & OOD


que pretenden una fusin de los esquemas estructurados con los de
orientacin-a-objetos, tales como "Synthesis" de Page-Jones & Weiss o el
"Object-Oriented Structured Design (OOSD)" de Wasserman.

C++/OOP: UN ENFOQUE PRCTICO Pgina 163/297


DIAGRAMANA

Resulta ciertamente descorazonadora la profusin de tantos elementos


diagramticos, escasamente diferenciados entre s, provenientes de los
esquemas grficos de SA/SD. Ocurre, as, que al unificar la orientacin-
-a-objetos las notaciones en el proceso entero de desarrollo, los autores de
los distintos mtodos, asumiendo la ventaja competitiva comercial que la
extensin del uso de stos efectivamente representa, los intentan
particularizar de forma forzosamente matizada: donde en un mtodo
aparece un crculo, en otros aparece una flecha o un cuadrado; donde un
cuadradado, en otros un crculo; etc.

Peter Coad directamente afirma: "Los tres ingredientes ms importantes de


cualquier proyecto son: gente, gente, gente. Quien desarrolla el trabajo es
ms importante que los mtodos (OOA, OOD) o las herramientas (CASE,
OOPLs)", as que recomienda "reclutar gente de calidad ... y despus proveer
mtodos y herramientas" [Coad91].

Dada la importancia que en todos los mtodos se le concede a la


identificacin de clases (u objetos, en un sentido ms amplio), las
especificaciones textuales propias de la etapa de OORA (Object-Oriented
Requirements Analysis) aparecen especialmente relevantes. De hecho, todos
los mtodos de OOA & OOD parten de tales especificaciones, de donde se
desprende que una adecuada representacin textual, quizs ayudada por un
simple soporte informal de comprensin grfica, podra resultar
autosuficiente para la gestin de las fases de OOA & OOD. Tanto es as, que
parece se est recuperando una cierta neurosis textual que, a la vez que
autodocumenta el proceso de desarrollo software, recoge la insatisfaccin
generada por la artificial practicidad de las actuales herramientas grficas.
Quiz el mtodo ms cercano a esta informalidad grfica sea el de
Wirfs-Brock: exploratorio, antropomrfico y basado en descripciones
textuales traspasadas a fichas CRC.

MODELADO DE JERARQUAS

Una parte importante e interseccin comn de las tcnicas de OOA y OOD


es la disposicin en jerarquas de herencia de las entidades modulares ya
identificadas. Tal y como se ha notado al principio de este trabajo, el autor
estima que la herencia es ms un mecanismo que una base conceptual, pero
hay que tener en cuenta que las tcnicas de OOD (como en su da las de SD)
provienen de la abstraccin y generalizacin de los comportamientos
observados en la OOP (eppore il muove...), de forma que si en la inmensa
mayora de los OOPL's se contempla tal mecanismo, parece claro que las
tcnicas de OOD debern tambin soportarlo en clara medida.

Las facilidades de reutilizacin modular procuradas por la herencia han


ocasionado lo que Cargill denomina "Problema de la herencia innecesaria" y

C++/OOP: UN ENFOQUE PRCTICO Pgina 164/297


que Rumbaugh simplemente tacha de "Abuso de la herencia". Es
desafortunadamente frecuente que, en las etapa de OOD, las relaciones
entre clases se modelen, sin restriccin ni control alguno, como derivativas
por herencia, lo que suele conducir a situaciones esencialmente errneas,
artificiosas e, incluso, peligrosas para la futura reutilizacin de los resultados
del diseo.

Naturalmente, las tcnicas de OOA/OOD tienden a procurar guias para el


correcto modelado de una jerarqua de este orden, en general de acuerdo
con el siguiente criterio: una relacin de herencia nicamente estar
verdaderamente cualificada cuando se produzca una especializacin o
refinamiento mediante la adicin en la subclase de comportamientos y/o
atributos distintos a los de la superclase de que aqulla deriva. Lo que, en
definitiva, se pretende instruir podramos sintetizarlo en la frase: "Una
relacin de herencia necesariamente ha de ser una relacin ES-UN". Pero
an esto podra, en ciertos mbitos, resultar confuso: de hecho, no todas las
relaciones del tipo ES-UN han de modelarse como herencia, (y aqu
entraramos en colisin con el mecanismo de layering, perfecta disyuntiva de
la herencia a los solos efectos de subencapsulacin modular semntica).

Uno de los errores ms comunes en el modelado de jerarquas se da al


considerar como herencia el refinamiento por restriccin. De esta manera
aparecen normalmente en textos introductorios a la OOP ejemplos errneos
del tipo: un cuadrado deriva (por hereda) de un rectngulo, o una circunfe-
rencia deriva de una elipse. Pinsese, verbigracia, que una circunferencia NO
ES-UNA elipse, por mucho que buena parte del cdigo de esta ltima clase
pueda ser empleado en aqulla: una cancelacin o modificacin, en
determinados entornos, de la condicin restrictiva podra evidenciar el
desajuste de comportamiento entre ambas clases. As ocurre, por ejemplo,
con el redimensionamiento de figuras en un editor grfico interactivo: si una
circunferencia mantiene el comportamiento de una elipse, la variacin de su
tamao originar por fuerza una elipse, al haber desaparecido, merced a la
propia operatividad del editor, la restriccin impuesta sobre la igualdad de
los dos radios en la elipse: pero un objeto no puede cambiar de clase!. Un
ejemplo ms claro, en el mismo entorno, lo podra dar la relacin entre
cuadrado y rectngulo, que, en ortodoxia conceptual, se limitara a compartir
una misma superclase.

Con el fin de proporcionar una guia, la aplicacin inseparable de los tres


siguientes criterios ayudar sobremanera a establecer la correctitud de un
modelo jerrquico:

La relacin entre dos clases ser modelada como herencia si y solo si


se cumple que:
1) la superclase mantiene una relacin de inclusin estricta con
respecto a la de su subclase.
2) donde quiera que aparezca un objeto de la subclase se puede
aplicar un objeto de la superclase.

C++/OOP: UN ENFOQUE PRCTICO Pgina 165/297


3) es necesario posibilitar la actuacin polimrfica de los objetos de
las clases en relacin.

La consideracin de la primera condicin pudiera obligarnos a reconsiderar


una relacin entre dos clases y abstraer las partes comunes en una nueva
superclase de ambas. El uso combinado de grficos derivativos y de
diagramas de Venn, tal y como propugna [Wirfs90], ayuda grandemente a
su comprobacin.

La segunda condicin asegura la filtracin de relaciones ES-COMO-UN,


ES-PARTE-DE, etc., a la vez que evita la herencia errnea por agregacin o
restriccin.

La ltima opcin establece, por ltimo, que, aun correspondiendo la


cualificacin de herencia a una relacin, sta no ser aplicada si no se
pretende tomar ventaja de tal mecanismo, pudiendo sustituirlo por otros
ms sencillos, como el de layering.

Adicionalmente cabra decir que en absoluto hay que minorar la importancia


de la herencia en un OOSS. James Rumbaugh ha llegado a afirmar que "un
modelo orientado-a-objetos es orientado-a-objetos porque el potencial de
aadir herencia al modelo est siempre presente". De hecho, en ciertos
autores, la jerarquizacin de reuso es denominada herencia sin restricciones
(unrestricted inheritance).

Una clarificadora sustanciacin del enfoque correcto del modelado de la


herencia aparece en [Bar-D92], que asimila las muchas definiciones de
herencia en dos categoras: herencia de comportamiento (semntica) y
herencia de implementacin, para concluir que las jerarquas deben
adscribirse a la primera.

DISEO ORIENTADO-A-OBJETOS DE BASES DE DATOS

Qu ocurre, desde la ptica de la orientacin-a-objetos, con el esfuerzo


tradicionalmente focalizado en el anlisis y diseo de bases de datos
relacionales adscritas al problema a modelar? La respuesta es sorpren-
dentemente simple: NO hay anlisis ni diseo sustantivo que aplicar a los
OODBMS's (Object-Oriented DataBase Manager Systems). Digamos que, en
una aproximacin peligrosamente intuitiva, los objetos sabrn cmo
archivarse y recuperarse en el espacio de una aplicacin: el gestor de la base
de datos tomar cuenta de estas y otras operaciones, liberando al
desarrollador de su modelado expreso. Naturalmente la revisin de las bases
de objetos (o con extensiones a objetos) exceden el mbito de este trabajo,
pero debido a su importancia merecen, al menos, una sucinta y elemental
descripcin extensiva.

C++/OOP: UN ENFOQUE PRCTICO Pgina 166/297


Los OODBMS's aaden a las facilidades del modelo relacional (modelo de
datos, persistencia, concurrencia, gestin de transacciones, recuperacin,
lenguaje de consulta, performance y seguridad) otras nuevas (abstraccin de
datos, potentes capacidades de modelado de informacin, identidad
objetual, encapsulacin y ocultacin de la informacin, datos activos,
herencia, funciones y datos polimrficos, composicin, paso de mensajes y
extensibilidad). En [Butte91] se resumen, por otra parte, las nuevas y
potentes capacidades transaccionales de los OODBMS: grandes
transacciones, transacciones anidadas, control optimizado de concurrencia,
concurrencia hbrida y versioning.

Tras lo expuesto, cabra preguntarse: verdaderamente el modelo


orientado-a-objetos sustituir al relacional en gestin de bases de datos?
Parece que s: la compaa londinense de consultora OVUM afirma en un
informe muy extendido titulado "Database for objects: the market
Oportunity" que las bases de datos relacionales con extensiones a objetos
llegarn a suponer el 52% del mercado total de DBMS's en 1.995, con un
mercado calculado en 4 billones de dlares entre USA y Europa. OVUM
estima, as mismo, que el mercado de las bases de objetos puras alcanzarn
los 560 millones de dlares en el mismo ao.

ALGUNAS HERRAMIENTAS OO-UPPER-CASE Y OO-I-CASE

La siguiente relacin no pretende ser exhaustiva, sino nicamente evidenciar


las tendencias de mercado en el soporte de las distintas metodologas de
OOA & OOD. Se ha obviado, de cualquier forma, la descripcin del soporte
de SA/SD por varias de las herramientas expuestas.

Herramienta Compaa Mtodos soportados

Objectory Objective Systems Objectory


System Architect Popkin Soft & Sys Booch, Coad/Yourdon
Teamwork OOA Cadre Technologies Shlaer-Mellor, HOOD
Teamwork OOD Cadre Technologies HOOD
OOATool Object International Coad/Yourdon
OODTool Object International Coad/Yourdon
Rational ROSE Rational Inc Booch
ILOG Kads Tool ILOG Common Kads
SES/Objectbench Scientif.& Eng.Software Shlaer-Mellor
001 Tool Suite Hamilton Technology Dev.Before the Fact
ObjectCraft ObjectCraft Inc ObjectCraft
Ipsys ToolBuilder Ipsys Ltd. HOOD
LOV/Object Edit. Verilog Rumbaugh
ObjecTime ObjecTime Ltd. ROOM
ObjectMaker Mark V Systems Booch, Coad/Yourdon,
Rumbaugh, Wirfs-Brock,
Shlaer-Mellor, ...

C++/OOP: UN ENFOQUE PRCTICO Pgina 167/297


Objecteering SofTeam Class Relation
OOSD/C++ IDE OO Structured Design
Stood TNI HOOD
Object M.B. IntelliCorp Martin & Odell OO IE
Paradigm Plus Protosoft Inc. Booch, Coad/Yourdon,
Rumbaugh, HOOD, EVB,
Fusion
OMTool General Electric Rumbaugh
Iconix P.T. Iconix Soft Eng. Rumbaugh, Booch,
Coad/Yourdon
Object Sys/Desig Palladio Software Inc Booch
OOther Roman Zielinksi Coad/Yourdon
TurboCase StructSoft Wirfs-Brock
ATRIOM Semaphore Booch, Coad/Yourdon,
Rumbaugh, Shlaer-Mellor
Foundation Andersen Consulting Foundation

REFERENCIAS

[Arnol91] Patrick Arnold, Stephanie Bodoff, Derek Coleman, Helena Gilchrist


& Fiona Hayes, "An evaluation of five object-oriented development
methods", Hewlett Packard Laboratories, HPL-91-52, 1991.

[Bar-D92] Tsvi Bar-David, "Practical consequences of formal definitions of


inheritance", JOOP, 5(4), 1992.

[Berar92] Berard Software Engineering Inc., "A Project Management


Handbook for Object-Oriented Software Development, Volume 1", Berard
Software Eng Inc, 1992.

[Berar92a] Berard Software Engineering Inc., "A Comparison of


Object-Oriented Development Methodologies", Berard Software Eng Inc,
1992.

[Booch91] Grady Booch, "Object-Oriented Design with Applications",


Benjamin/Cummings, 1991.

[Butte91] Paul Butterworth, "ODBMS as database managers", JOOP, 4(2),


47-50, 1991.

[Coad91] Peter Coad, "Why use object-oriented development (A


management perspective)", JOOP, 4(6), 1992.

[Hende90] Brian Henderson-Sellers, "A Book of Object-Oriented


Knowledge", 1990, Prentice Hall.

C++/OOP: UN ENFOQUE PRCTICO Pgina 168/297


[Meyer88] Bertrand Meyer, "Object-Oriented Software Construction", 1988,
Prentice Hall.

[Rumba91] James Rumbaugh, Michael Blaha, William Premerlani, Frederick


Eddy & William Lorensen, "Object-Oriented Modeling and Design", 1991,
Prentice Hall.

[Wasse91] Anthony I. Wasserman, "Object-Oriented software development:


issues in reuse", JOOP, 4(2), 55-57, 1991.

[Wasse91a] Anthony I. Wasserman, "From Object Oriented Analysis to


Design", JOOP, 4(5), 1991.

[Wirfs90] Rebeca Wirfs-Brock, Brian Wilkerson & Lauren Wiener, "Designing


Object-Oriented Software", 1990, Prentice Hall.

C++/OOP: UN ENFOQUE PRCTICO Pgina 169/297


A-4
INSISTENTE
PERSISTENCIA

La persistencia se ha convertido en la palabra mgica alrededor de la que se


han aglutinado sistemas, diseos, expectativas y, finalmente, un cierto
desencanto. Ante la lectura de algunas publicaciones tcnicas podra afirmar-
se, parafraseando a Swift, que "es tan difcil librarse de la persistencia como
del infierno". El no buscado hermetismo conceptual habitualmente aplicable
a los sistemas de objetos se ha trocado en opaca y esponjosa tibiedad
envolviendo al concepto de persistencia y convirtindolo en borrosa zona de
convergencia de distintas metodologas para el archivo y acceso de datos,
obviando la conceptualizacin terica comn a los distintos esquemas
prcticos implementados. El concepto se ha modelado, en definitiva, por
inferencia implcita basada en los desarrollos prcticos "propietarios" de
distintas empresas y entidades, adoleciendo de un soporte axiomtico
propio.

Encontramos una brevsima aproximacin a la persistencia como cualidad


general de los objetos en [Meyer88], aunque en este texto se resolva su
discusin asimilando tal cualidad a las propiedades implementadas en Eiffel
mediante la clase Storable. El Dr. Meyer incida puntualmente sobre el
interesante problema de la recuperacin a la memoria de objetos cuyas
clases han cambiado desde el momento de su archivo. Por ltimo, y
teniendo en cuenta que en aquel momento empezaban a despuntar los
albores de la futura gran eclosin de ODBMSs (Sistemas Gestores de Bases
de Datos de Objetos, tambin denominados OMSs: Sistemas Gestores de
Objetos) , estimaba improbable que las tcnicas de extensin de la
persistencia a objetos pudieran mejorar la efectividad de los esquemas de
bases de datos tradicionales (relacionales, jerrquicas, de red, de relacin-
entidad). Las ltimas pruebas realizadas sobre ODBMSs han arrojado,
empero, una relacin de efectividad en archivo y recuperacin de objetos
comprendida entre 36:1 y 100:1 con respecto a los procedimientos
homlogos sobre equiparables estructuras de datos tradicionales basados en
RDBMSs (Sistemas Gestores de Bases de Datos Relacionales). Tales
resultados han situado la persistencia en un contexto ms atractivo,
enfatizando el desarrollo de distintas aproximaciones al concepto.

C++/OOP: UN ENFOQUE PRCTICO Pgina 170/297


La persistencia es todava, no obstante, "la gran desconocida": en un texto
de OOA&OOD importante cual es [Rumba90] el tratamiento que se da a esta
propiedad es poco menos que trivial y, an as, limitado a escasas lneas, y
no muy inferiores en nmero a las dedicadas a los que en el texto se
denominan OO-DBMSs y que se asimilan como heterodoxa unin de un
OOPL y de persistencia de datos.

En el presente anexo abordaremos una propuesta de formalizacin terica


de la persistencia en subsistemas de objetos. Repasaremos, seguidamente,
uno de los esquemas de persistencia ms extendidos, comentando en detalle
la implementacin adoptada por Borland en sus libreras-entornos de clases
para Borland C++ 3.1. Examinaremos, por ltimo, un mtodo automtico,
basado en el procedimiento anterior, para aadir persistencia a las clases
definidas en un sistema C++.

FUNDAMENTOS CONCEPTUALES

La Gua de Arquitectura de Gestin de Objetos confeccionada por el OMG


(Object Management Group) establece el siguiente tenor literal:

"Los objetos persistentes se utilizan para representar el estado a largo


plazo de una computacin en ejecucin, como por ejemplo el
conjunto de empleados de una corporacin y las relaciones entre los
mismos. Tales instancias poseen, por ejemplo, la cualidad intrnseca
de sobrevivir al proceso que las cre; incluso sobreviven a la extincin
temporal de la CPU que las gener.

Se identifica aqu, pues, la persistencia con la cualidad de algunos objetos de


mantener su identidad y relaciones con otros objetos con independencia del
sistema o proceso que los cre. Tal propiedad supone, por ejemplo, la
congelacin temporaria de un determinado proceso inter-objetos (pensemos
en una gran transaccin a realizar por un ODBMS).

El concepto de persistencia aparece intuitivamente ligado al de


almacenamiento, sosteniendo nicamente como necesario corolario al
proceso de recuperacin. En realidad ambos procesos forman parte de la
cualidad definida como persistencia. En adelante, pues, hablaremos de
persistencia, o impulsin de persistencia (concepto fuertemente ligado a los
sistemas persistentes dinmicos), ms que de archivo o recuperacin de
objetos. El sistema determinar en cada momento, de forma transparente
para el usuario en el caso ms general, si debe usar el almacenamiento
persistente secundario o primario.

La persistencia de un sistema de objetos se implementa, en la prctica,


proveyendo a stos de un mecanismo cuyo objetivo bsico consiste tanto en
el almacenamiento de objetos existentes en la memoria como en la

C++/OOP: UN ENFOQUE PRCTICO Pgina 171/297


recuperacin posterior a sta de los mismos. La eficiencia es un factor crtico
fuertemente ligado a tal mecanismo, que supone la preservacin no slo de
los datos estrictamente contenidos en un objeto (de tipo incorporado e
instancias de otras clases) sino tambin de la identidad del mismo
(determinada tanto por las caractersticas semnticas de tipo adquiridas
mediante herencia -derivacin en C++- como por las relaciones contextuales
con respecto a funciones con ligadura dinmica -tabla de funciones
miembro virtuales en C++-), as como de las relaciones, apuntadores y
referencias a otros objetos. Definiremos a efectos prcticos la persistencia,
pues, como la cualidad de un determinado objeto de almacenar y
recuperar eficiente, automtica y selectivamente la estructura
compleja de objetos relacionados con el mismo (incluimos aqu,
naturalmente, la auto-relacin). De acuerdo con tal definicin la
aplicacin de la persistencia a un objeto inmerso en una estructura
equivaldra a la impulsin de un primer mensaje al mismo que, a su vez y en
razn de un mecanismo de extensibilidad que veremos ms adelante,
posiblemente generara otros mensajes dirigidos a sendos objetos de la
estructura y dara lugar al trazado figurado de un complejo grafo derivativo
en la estructura en s, transformndose as en un mensaje que recorrera la
estructura de objetos procediendo bien al almacenamiento bien a la
recuperacin en memoria de los objetos calificados como persistentes.

Como vemos, la persistencia en un slo objeto constituira nicamente un


caso particular de restriccin selectiva de la extensibilidad de la ms general
cualidad de persistencia aplicable a una estructura de objetos: o bien el
objeto en s no contiene ni apunta o referencia a otros objetos, o bien
implcita o expresamente se han declarado tales objetos, directamente o a
travs de referencias o punteros, como transitorios (transient objects). As
observamos que la persistencia modela distintos grados de extensibilidad
inter-objetos que podran ser finamente granulados, permitiendo as una
gran flexibilidad en la implantacin de la misma.

Por extensin, la cualidad de persistencia habr de aplicarse, pues, a la


estructura en s como caso ms general. Intentaremos establecer
seguidamente, as, el soporte terico bsico de la persistencia de sistemas de
objetos.

IMPULSIN DE PERSISTENCIA

Examinemos en primer lugar la cualificacin de la impulsin inicial de


persistencia: Debe ser sta un mensaje dirigido a un objeto de la
estructura, que contara con un mtodo recursivo propio o heredado para su
aplicacin extensiva? O ms bien debe constituirse en un mensaje que el
objeto al que ha de aplicarse la impulsin enviar a otro objeto, que
denominaremos manipulador de persistencia, soportando ste las
caractersticas concretas de la persistencia deseada?

C++/OOP: UN ENFOQUE PRCTICO Pgina 172/297


Evidentemente el primer mtodo ligara de forma indeleble las caractersticas
fsicas del sistema de persistencia elegido a la definicin de las clases
implicadas, vulnerando as uno de los principios bsicos de la OOP: cambios
pequeos han de tener pequeas repercusiones. En un tal sistema un
pequeo cambio en el sistema de archivo y recuperacin de objetos
implicara una modificacin del cdigo tanto ms importante cuanto ms
extenso sea el conjunto de objetos afectados. La extremada concreccin que
podra derivarse del conocimiento profundo de la estructura interna de una
clase podra, por otra parte, resultar en una demasiado ajustada
implementacin de la persistencia, que a su vez podra redundar en
detrimento de la reutilizacin de tal cdigo. Hay que considerar, no obstante,
la posibilidad que cada cdigo de esta forma implantado revierta en un
objeto o estructura de objetos exterior en calidad de gestor de persistencia,
de forma que esta primera posibilidad se convierta en un caso especial de la
segunda opcin. En general ste es el procedimiento usado cuando la
persistencia se direcciona siempre a travs del mismo gestor o persistence
manager, cual es el caso habitual en los ODBMSs.

El segundo procedimiento usara de manipuladores persistentes: objetos


autnomos o insertos en una estructura gestora de persistencia. A un nivel
elemental, ste es el trabajo realizado, por ejemplo, por los objetos ofstream
e ifstream cuando se les remiten los mensajes de extraccin (<<) e insercin
(>>) respectivamente:

ofstream opo("archivo.001", ios::out );


// inserta un objeto en un objeto ofstream
opo << objetoDeTipoX;
ifstream ipo("archivo.002", ios::in );
// extrae un objeto de un objeto ifstream
ipo >> objetoDeTipoY;

Este mtodo exige que los objetos gestores de persistencia pmgs


(persistence managers) sepan cmo tratar a cada objeto. De acuerdo con el
anterior cdigo esto supondra que en la descripcin de las clases de los
respectivos pmgs aparecieran unas funciones definidas posteriormente como
las siguientes:

ofstream& ofstream::operator <<( const TipoX& x ) { /* ... */ }


ifstream& ifstream::operator >>( const TipoY& y ) { /* ... */ }

o posiblemente tambin como

inline ifstream& operator >>( ifstream& iso, const TipoY& y )


{ /* ... */ }

En realidad, el hecho de tener que variar las clases de los pmgs cada vez que
se deseara dotar de persistencia a un objeto (obviaremos aqu las facilidades
de derivacin) atenta contra el ncleo terico de la OOP, generando una
fcilmente monstruosa macro-clase virtualmente no-reutilizable. As, en la

C++/OOP: UN ENFOQUE PRCTICO Pgina 173/297


prctica, se direcciona el control de la persistencia hacia un mtodo
encapsulado en el mismo objeto inicial del subsistema que se desea
persistente, el cual, a su vez, convenientemente revierte el control de nuevo
al pmg, bien para cada objeto de la secuencia impulsora o bien slo para los
tipos incorporados, y siempre una vez finalizada la impulsin. De hecho
estamos reiterando el primer esquema propuesto, pero con la posibilidad de
cambiar en tiempo de ejecucin el pmg. De cualquier forma esta
caracterstica tambin podra ser implementada en la primera opcin
aadiendo, por ejemplo, un nuevo parmetro a cada mtodo de persistencia
encapsulado en una clase que soportara el paso de objetos pmg.

La anterior revisin ha confluido en resaltar la igualdad del sustrato bsico


sobre el que se asientan las dos opciones de impulsin de persistencia. La
eleccin de una u otra depende ms de consideraciones de diseo y estilo
que de diferencias metodolgicas. Ms adelante examinaremos distintas
posibilidades prcticas.

SISTEMAS PERSISTENTES

Los objetos de un sistema interrelacional COS (Complex Object Structure)


afectados a travs de la aplicacin de la persistencia en un primer objeto psg
(persistent subsistem generator) (initial persistence object) constituirn un
subsistema POSS (Persistent Object SubSistem) de la estructura general que
calificaremos como susbsistema persistente en COS por psg y que
notaremos como

persistent<COS:psg>POSS

Tomemos como ejemplo el siguiente conjunto "OBS" de objetos, de cuyas


identidades haremos abstraccin, constitutivo de una estructura compleja de
relaciones mutuas:

OBS={objP1, obj2, objP3, objP4, obj5, obj6, objP7, obj8, objP9}

donde objN representa la instanciacin de una determinada clase y objPN un objeto dotado de persistencia. Si
dirigimos la impulsin de persistencia a, por ejemplo, el objeto objP4, ste distribuir tal mensaje a travs de la
estructura (para simplificar obviaremos en este estadio las referencias cclicas y la redundancia de objetos),
causando el archivo persistente de, verbigratia, los objetos objP1, objP3 y objP9. Representaremos el
subsistema afectado SOBS con la siguiente notacin:

persistent<OBS:objP4>SOBS { objP1, objP3, objP4, objP9 }

y calificaremos a objP4 como un objeto spg generador del mismo. Denominaremos orden de un
subsistema persistente, por otro lado, al nmero de objetos generadores del mismo, y lo notaremos como

o(persistent<COS:iob>POSS)

C++/OOP: UN ENFOQUE PRCTICO Pgina 174/297


de forma que, evidentemente, si n es el nmero total de objetos del
subsistema persistente, siempre se cumple que

1 <= o(persistent<COS:iob>POSS) <= n

En el caso que el orden de un subsistema sea igual al nmero de objetos en


l contenidos (o(POSS)=n), todos los objetos del subsistema seran generadores del mismo, recibiendo
ste el nombre de subsistema persistente cclico CPOSS (Ciclyc Persistent Object SubSistem), siendo
representado por la notacin

persistent<OBS>CPOSS

Supuesto que el orden de persistencia del anterior subsistema sea mayor de


uno y dados dos generadores objP4 y objP9 del mismo, siempre se cumplir que

persistent<objP4:OBS>POSS == persistent<objP9:OBS>POSS

Por el contrario la representacin fsica del almacenamiento del subsistema


persistente depender, en su caso ms general, del generador utilizado. Si la
representacin fsica de un subsistema generada por dos distintos objetos
psg es la misma, tales objetos se denominarn generadores
conmutativos de persistencia del sistema. El conjunto de los
generadores conmutativos de un subsistema dado se denominar ncleo
del subsistema. Cuando el conjunto de generadores coincida con su
ncleo, al subsistema generado se le denominar subsistema
persistente.

En realidad el sistema axiomtico esbozado soportara una cualidad general


de persistencia caracterizada por las propiedades de concurrencia, manteni-
bilidad, inspeccionabilidad, reutilizabilidad de cdigo, almacenamiento
multinivel y estructuracin dinmica. Como quiera que el somero anlisis de
tales singularidades sobrepasara con mucho el mbito y las posibilidades de
este anexo, se remite al lector a [Devis94], donde se desarrolla en detalle
esta propuesta de formalizacin axiomtica de la persistencia.

LA PERSISTENCIA SEGN BORLAND

Borland ofrece en la versin profesional de su compilador Borland C++ 3.1


una particular implementacin de la persistencia esttica de objetos, basada
en la librera NIH expuesta en [Gorle90] y apoyada sobre la librera estndar
iostream de C++. En realidad Borland ofrece dos versiones de tal
mecanismo de persistencia insertadas en sus entornos comerciales (ms que
libreras) TurboVision for C++ y ObjectWindows for C++ (ofrecido ste
ltimo para su estandarizacin por el OMG), aunque ambas comparten los
mismos identificadores, estructuras y procedimientos (a excepcin de
algunos typedefs). El lector podr encontrar en [Urloc91] una
comprehensiva y sucinta presentacin de los entornos-marco de Borland.

C++/OOP: UN ENFOQUE PRCTICO Pgina 175/297


Examinaremos, pues, en lo que sigue, nicamente una de tales
implementaciones: la referida a ObjectWindows for C++, pudiendo
fcilmente los lectores mapear todo lo expuesto al entorno para DOS.
Intentaremos ahondar, por fin, en las consideraciones de diseo y metodolo-
ga subyacentes en la construccin de tal sistema comercial, favoreciendo, en
lo posible, aproximaciones mejoradas al mismo.

El mecanismo de persistencia adoptado en ObjectWindows es dual,


esttico, y derivativo. Veamos tales caractersticas en detalle:

El mecanismo de persistencia es dual: los objetos y sus DAGs (Direct


Acyclic Graph) asociados deben ser expresamente archivados desde la
memoria al almacenamiento persistente secundario (ficheros persistentes o
streams) y expresamente recuperados desde ste a la memoria de la
aplicacin, o almacenamiento persistente primario.

Decimos que el mecanismo es esttico porque implica que la cualidad de


persistencia no pueda ser aadida o cambiada a un objeto en tiempo de
ejecucin. Esto es, se trata de una operacin sobre las clases y no sobre los
objetos. Los subsistemas persistentes quedan establecidos, as, en tiempo de
compilacin por la pertenencia de los objetos a determinadas clases
persistentes. La persistencia esttica de objetos deriva, pues, en el concepto
de persistencia de clases. De esta manera los OIDs (Object Identifications)
se trocan en CIDs (Class Identifications), y los mecanismos de identificacin
en tiempo de ejecucin de un objeto con respecto al subsistema persistente
son establecidos en tiempo de compilacin y comprenden informacin lgica
nicamente.

El sistema persistente se califica como derivativo porque los mtodos de


archivo y recuperacin de objetos se implementan en las clases a los que
stos pertenecen de forma que, para evitar una excesiva dependencia de los
mtodos fsicos, se hacen derivar de una clase

De acuerdo con lo expuesto, si deseamos convertir en persistentes a todos


los objetos de una determinada clase debemos, en primer lugar,
expresamente aadir a su descripcin la derivacin pblica de la clase
TStreamable:

class MiClasePersistente : [MiClaseBase,]* public TStreamable


{ /* ... */ };

A la clase se le "adhieren" entonces, por derivacin, las funciones miembro


de TStreamable, definidas todas ellas como funciones virtuales puras bajo
las cualificacionesde acceso protected y private:

protected:virtual void* read( ipstream& ) = 0;


private:virtual const char* streamableName() const = 0;
protected:virtual void write( opstream& ) = 0;

C++/OOP: UN ENFOQUE PRCTICO Pgina 176/297


Lo que se pretende con este mecanismo es forzar la redefinicin de tales
funciones o su redeclaracin como funciones virtuales puras en la clase
derivada. Este comportamiento, ajustado a la versin AT&T C++ 2.1
cambia, sin embargo, en AT&T C++ 3.0 permitiendo la herencia por defecto
de las funciones virtuales de clases base en clases derivadas, que seran as
declaradas abstractas. Esta matizacin no supone, empero, cambio alguno
en el mecanismo de persistencia, a no ser la ocasional desaparicin del
chequeo por error en compilacin debido a la no redefinicin de tales
funciones virtuales.

Debemos, pues, retomando el procedimiento, redefinir tales funciones en la


clase a la que queremos dotar de persistencia. Y tal redefinicin se
implementar en directa dependencia y consecuencia del mtodo de
recuperacin de objetos elegido. En este caso la aproximacin a la
persistencia se ha significado mediante la descomposicin de los objetos en
entidades de tipo incorporado (int, char, etc.) delimitadas por identificadores
de clase. El mecanismo de recuperacin de objetos se limitara a clonar
objetos creados a partir de tales identificadores copiando recursivamente sus
miembros: los de tipo incorporado seran copiados directamente, mientras
que los objetos apuntados, contenidos o referenciados seran recuperados
desde el stream por el mismo procedimiento. Supondremos en lo que sigue
que las funciones virtuales de TStreamable han sido ya redeclaradas en
nuestra clase aspirante-a-persistente, de forma que nos ocuparemos
nicamente de su definicin fuera del mbito de descripcin de la clase.

En primer lugar tenemos que proporcionar un identificador a la clase


compartido por todos sus objetos y accesible en tiempo de ejecucin: esto
se consigue mediante la funcin

inline const char* MiClasePersistente:streamableName() const


{ return "MiClasePersistente"; }

que devuelve el nombre de la clase, declarada en la seccin private de sta.


En los mecanismos de persistencia derivados de la librera NIH existen
funciones parejas que, bsicamente por razones de eficiencia, devuelven un
nmero en lugar de una cadena. Una solucin alternativa consistira en
aadir un nuevo miembro a la clase aspirante-a-persistente:

public: static const char* const name;

el cual sera accedido por medio de la funcin definida as:

class MiClasePersistente : public TStreamable {


private:
virtual const char* streamableName() const {return name;}
};

supuesta la declaracin, en algn lugar del cdigo, de la asignacin:

C++/OOP: UN ENFOQUE PRCTICO Pgina 177/297


const char* const MiClasePersistente::name="MiClasePersistente";

La declaracin en la seccin private restringe selectivamente el uso de la


funcin streamableName() a las clases friend opstream e ipstream.

LA FUNCIN ESCRITORA

El siguiente paso es definir, en la seccin public, la funcin de almacena-


miento o escritora (write()) sobre el soporte de dos formas de indireccin recursiva: el mtodo de
almacenamiento de la porcin del objeto heredada por derivacin se desva hacia las correspondientes clases
base, mientras que el procedimiento de archivo de los objetos de tipo no-incorporado se direcciona hacia stos,
forzando la persistencia tanto de las clases base como de las correspondientes a objetos contenidos en la clase
aspirante-a-persistente. El argumento de la funcin escritora es una referencia a un objeto de clase opstream
(output persistent stream), derivada de pstream para operaciones de insercin, donde pstream es la clase base
de la versin estructural "persistente" de la librera iostream. Realmente la diferencia entre ambas libreras radica
en el tratamiento que la clase pstream realiza con los datos antes de su direccionamiento al buffer streambuf
definido en iostream. Veamos un molde de la funcin:

void MiClasePersistente::write( opstream& ops )


{
MiClaseBase::write( ops );
ops << objetosDeTipoIncorporado;
ops << objetoDeOtraClasePersistente;
ops << punteroAObjetoDeOtraClasePersistente;
}

Este planteamiento exige, pues, que la descripcin de MiClaseBase haya sido


convenientemente adaptada para soportar el mismo tipo de persistencia. De esta forma deberamos declarar
recursivamente

class MiClaseBase : MiOtraClaseBase,


public TStreamable { /* ... */ };
class MiOtraClaseBase : OtraClaseBaseMas,
public TStreamable { /* ... */ };
// etc., etc.

Tal mecanismo induce, sin embargo, la siguiente penalizacin en su


aplicacin a estructuras de clases interrelacionadas por derivacin como, por
ejemplo, los entornos de clases Smalltalk-like: la continua aplicacin de la
derivacin en tales clases implica la insistente e incremental repeticin de la
porcin correspondiente a la clase TStreamable en todas y cada una de las
clases candidatas-a-persistentes, lo cual, dado el anterior cdigo y
suponiendo la derivacin mltiple siguiente:

class MiClaseSospechosa: public MiClaseBase,


public MiOtraClaseBase { /* ... */ }

podra dar lugar a un error por ambigedad en la llamada, por ejemplo, a


una funcin que tome como argumento un objeto o referencia a un objeto

C++/OOP: UN ENFOQUE PRCTICO Pgina 178/297


del tipo de esta clase. Consideremos una de las definiciones del operador de
insercin:

opstream& operator <<( opstream& ops, TStreamable& t )


{ /* ... */ }

Podemos aislar el problema en el siguiente cdigo:

MiClaseSospechosa objetoSospechoso;
// la prxima lnea ocasionar un error por ambigedad,
// pues el objetoSospechoso posee al menos dos
// porciones TStreamable: la obtenida por derivacin
// de MiClaseBase y la derivada de MiOtraClaseBase.
// Cul emplear?
ops << objetoSospechoso; // ERROR
// el siguiente cast resolvera la ambigedad
ops << (MiOtraClaseBase)objetoSospechoso;

Cabran dos posibles soluciones: bien trocar cada derivacin en derivacin


virtual, bien aplicar la derivacin de TStreamable nicamente a la clase base
del entorno derivativo. Dada la singularidad esttica del mecanismo aqu
estudiado, aparece ms apropiada la segunda opcin, aplicable en todo caso
a la clase base de la que suelen derivarse en su estadio inicial de diseo las
restantes clases de un proyecto: tal clase se constituira, as, adems de en
raz del mecanismo de depuracin (debug) del sistema en la base de
persistencia del mismo. Hay que notar que la declaracin de una derivacin
dada de TStreamable como virtual ocasionara una dificultad adicional: dado
que el mecanismo de gestin de la persistencia est basado en las facilidades
provistas por la clase TStreamable, es frecuente el cast operacional a tal
clase de los objetos persistentes; la derivacin virtual ocasiona, sin embargo,
que la clase base afectada sea accedida por medio de un puntero, de manera
que un cast a TStreamable equivaldra al recorrido del puntero en sentido
inverso: esto supone, evidentemente, la inaccesibilidad directa a la porcin
de la clase base. La accesibilidad indirecta podra ser implementada, con
todo, mediante un mecanismo que recorriera el esquema jerrquico y
estableciera lo que se denominan relacionamientos inversos, en un esquema
de correspondencia parecido al empleado en la librera NIH.

Naturalmente en estructuras complejas de derivacin mediante herencia


mltiple cabra la aplicacin selectiva simultnea de las dos opciones. De esta
manera, y supuestas las circunstancias descritas, el cdigo anterior podra
reescribirse de la siguiente forma:

class MiPrimeraClaseBase :
[claseBaseNoPersistente,]* virtual public TStreamable
{ /* ... */ };
class MiSegundaClaseBase :
[otraClaseBaseNoPersistente,]* public TStreamable
{ /* ... */ };
class MiClasePersistente : public MiPrimeraClaseBase,

C++/OOP: UN ENFOQUE PRCTICO Pgina 179/297


public MiSegundaClaseBase
{ /* ... */ };

Otra posibilidad consistira en dejar invariante la declaracin de la clase que


queremos promover a persistente, creando una nueva clase derivada de sta
y de TStreamable, como por ejemplo:

class MiClase [: [MiClaseBase [,OtrasClasesBase]*]]


{ /* ... */ };
class MiClasePersistente : public MiClase, public TStreamable
{ /* ... */ };

Naturalmente la penalizacin inherente a tal desarrollo consiste en la


re-escritura de los constructores apropiados a la nueva clase. Tal enfoque
aparece apropiado, sin embargo, para la aplicacin de propiedades
bien-definidas (como la que nos ocupa) en entornos Smalltalk-like en los
que se enfatice el uso bsico de los constructores por defecto (como por
ejemplo los basados en la arquitectura MVC: Model-View-Controller), pues
como quiera que, como es sabido, la no-intervencin explcita en la
inicializacin de las clases base respecto de los constructores de una clase
dada asegura el uso del constructor por defecto de aqullas, tal aspecto
redundara en una deseable simplificacin del cdigo a la vez que
encapsulara en una clase aparte los detalles especficos de persistencia
dejando invariante el cdigo ya escrito. Una tal reunin de clases
persistentes por herencia mltiple resultara en una estructura paralela
persistente con respecto a las clases iniciales sobre la que, de nuevo, cabra
aplicar la disyuntiva prctica establecida en el prrafo anterior.

Retomemos, salvado el inciso, la definicin de la funcin escritora: nos ha


faltado aadir el tratamiento de insercin en el objeto opstream de los
miembros, incorporados o no, de tipo agregado, tales como arrays, sets,
listas encadenadas y, en general, contenedores de objetos. Generalizando,
tales casos habran de reducirse a los ya vistos mediante la aplicacin
ordenada de la funcin escritora a cada uno de los objetos contenidos en el
objeto de tipo agregado, accesibles a travs de iteradores o, en un nivel
ms alto, de funciones diseadas para recorrer apropiadamente los objetos.
En los arrays de objetos (de tipos tanto incorporado como
definido-por-el-usuario) el sistema no aporta iterador propiamente dicho, de
forma que la navegacin a travs de los objetos contenidos en el array es
responsabilidad del desarrollador. La subfuncin escritora del array habr de
ser implementada, pues, como una estructura de control. Los objetos de tipo
agregado-definido-por-el-usuario observarn, a los presentes efectos, el
control de su propia funcin escritora. Veamos, pues, en la prctica, los
distintos supuestos ms detalladamente:

const MAXFILA = 2;
const MAXCOL = 4;
class PCliente;
class ArrayPersistente;

C++/OOP: UN ENFOQUE PRCTICO Pgina 180/297


class MiClase {
float floatArray[MAXFILA][MAXCOL]
PCliente PClienteArray[MAXFILA][MAXCOL]
ArrayPersistente miArrayPersistente;
// ...
};
// ...
class MiClasePersistente : public MiClase,
virtual public TStreamable
{ /* ... */ };
void MiClasePersistente::write( opstream& ops )
{
// ...
for ( int m = 0; m<=MAXFILA; m++ )
for ( int n = 0; n<= MAXCOL; n++ ) {
ops << floatArray[ m ][ n ];
ops << PClienteArray[ m ][ n ];
}
ops << miArrayPersistente;
// ...
}
// ...
class ArrayPersistente: public Array, public virtual TStreamable
{ /* ... */ };
void ArrayPersistente::write( opstream& ops )
{
ArrayIterator miIterador( *this );
StringPersistente miStringPersistente;
miIterador.restart();
while ( miStringPersistente =
(StringPersistente*)miIterador++ )
ops << miStringPersistente;
}

Hemos utilizado en la exposicin las clases Array y ArrayIterator, incluidas


en la Librera de Clases Contenedoras de Objetos (Object Container Class
Library), un entorno Smalltalk-like incluido en la versin profesional de
Borland C++ 3.1. As, vemos que los detalles especficos de manipulacin
de los elementos internos de un objeto son tratados por mtodos encapsula-
dos en los mismos: esta es, en definitiva, la esencia del paradigma de OOP.

De acuerdo con lo expuesto, indicaremos que, bsicamente, la operacin de


insercin de un objeto derivado de TStreamable en un objeto de tipo (o
derivado de) opstream resulta en el siguiente cdigo:

opstream& operator <<( opstream& ops, TStreamable& t )


{
ops.writePrefix( t ); // [streamableName()
ops.writeData( t ); // chequea registro de Clase
// y escribe miembros internos
ops.writeSuffix( t ); // ]
return ops;
}

C++/OOP: UN ENFOQUE PRCTICO Pgina 181/297


Pero, qu ocurre cuando el objeto que pretendemos persistente contiene
uno o ms punteros a otros objetos, persistentes o no? Vemoslo
detalladamente.

Imaginemos una clase conteniendo un dato miembro significado en un


puntero a una clase no establecida como persistente. Como quiera que la
persistencia que aqu tratamos es esttica, la cualificacin de un determinado
objeto miembro como transient podra determinarse de forma indirecta por
su no inclusin en la funcin escritora, en cuyo caso la nica restriccin ser
la de procurar la inicializacin a cero de tales punteros en la construccin de
los objetos contenedores de los mismos durante el proceso de lectura. Pero
esto no resultara muy conveniente, pues generara una independencia entre
los procesos de lectura y escritura difcil de documentar. Ms apropiada sera
la "nulificacin" del puntero en la funcin escritora, o ms explcitamente
mediante la siguiente codificacin:

// la siguiente lnea aparecera en


// cualquier lugar de <objstrm.h>
const int TRANSIENT = 0;
// ...
void MiClasePersistente::write( opstream& ops )
{
// ...
ops << ( punteroAObjetoNoPersistente = TRANSIENT );
}

La transcripcin escritora de un puntero nulo sera, por otra parte, transpa-


rente, pero, qu ocurrira cuando lo que se deseara insertar en un opstream
fuera un puntero a un objeto persistente? Un tal objeto podra ser mltiple-
mente apuntado por otros objetos, de forma que si decidiramos asociar
cada aparicin en un objeto de un puntero al mismo con una operacin
individual de insercin, el procedimiento devendra costosamente inefectivo.
Una posible solucin consistira en la habilitacin de un almacenamiento
temporal donde fueran registrados los distintos objetos encontrados en la
secuencia de insercin y antes de su escritura. A cada uno de tales objetos,
representados por sus direcciones en memoria, se les asociara un ndice o
contador, significativo de su orden de registro en tal almacenamiento, de
forma que, dado un puntero a un objeto y antes de proceder a su escritura,
se buscara en el objeto contenedor o almacenador el par (direccion, indice)
que casara con la direccin contenida en el puntero dado, resultando en la
siguiente bifurcacin: si el objeto apuntado no estuviera registrado (esto es,
si no existiera el par con clave direccionDeBusqueda), se procedera a su
registro e insercin, desreferenciando el puntero, en el pstream; si, por el
contrario, se encontrara registrado se procedera a escribir la referencia
ordinal del mismo indicando su situacin como entidad en la escala
secuencial escritora. Examinemos seguidamente la implementacin que
Borland realiza de este esquema.

C++/OOP: UN ENFOQUE PRCTICO Pgina 182/297


En la librera que nos ocupa el objeto almacenador es un contenedor de tipo
TPWrittenObjects, una clase derivada pblicamente de TNSSortedCollection,
que a su vez representa un contenedor ordenado de objetos de clases no
derivadas de TStreamable (no olvidemos que lo que se almacenar ser la
representacin no-persistente de los objetos persistentes a insertar en el
pstream). En el objeto de tipo TpWrittenObjects se insertarn objetos
TPWObj, encapsuladores de los distintos pares (direccion, indice)
construidos a partir de los objetos secuencialmente dispuestos para ser
escritos en el pstream. Este mecanismo incorpora, adems, con relacin al
expuesto en el prrafo anterior, un nivel adicional de optimizacin, pues los
objetos persistentes contenidos en objetos a insertar en opstreams son
tambin registrados y referencialmente insertados como objetos TPWObj en
la base de datos: de esta forma todos los objetos persistentes ya escritos y
apuntados por punteros no sern duplicados en el pstream. Esto sugiere que
el cdigo para el registro de los objetos habr sido desplazado desde el
bloque de insercin de punteros hasta el bloque de insercin de objetos y,
de hecho, se encuentra en el cuerpo de la funcin void opstream::writeData(
TStreamable& ).

La casustica de insercin de punteros a objetos persistentes es tratada de la


siguiente forma: en la seccin public del cuerpo de descripcin de la clase
pstream se declara:

enum PointerTypes { ptNull, ptIndexed, ptObject };

siendo utilizados tales enumeradores como sealizaciones en el pstream de


la modalidad de escritura utilizada. Examinemos el cdigo:

opstream& operator << ( opstream& ops, TStreamable *t )


{
unsigned index;
if( t == 0 ) // puntero nulo puntero
// a objeto Transient
ops.writeByte( pstream::ptNull );
// seguidamente se busca un TPWObj conteniendo
// (void*) address == t
// Si la bsqueda tiene xito se devuelve el
// ordinal de escritura del objeto
else if( (index = ps.find( t )) != 0 ) {
ops.writeByte( pstream::ptIndexed );
// se escribe el ndice orginal
ops.writeWord( index );
} else { // insercin normal como objeto persistente
ops.writeByte( pstream::ptObject );
ops << *t;
}
return ops;
}

Esta estructuracin permitir la adecuada extraccin en su momento de los


objetos alojados en el pstream.

C++/OOP: UN ENFOQUE PRCTICO Pgina 183/297


Nos resta nicamente considerar un aspecto: debe establecerse un mecanis-
mo de seguridad que compruebe si el sistema posee el cdigo adecuado
para la recuperacin o extracin de cada uno de los objetos insertados en el
pstream. Como veremos al considerar la funcin lectora, en las clase
persistentes debe aadirse el cdigo general

TStreamableClass RegPClase ( "TPClase",


TPClase::build,
__DELTA( TPClase ) );

el cual originar el registro de la clase (indexado por medio de streamable-


Name()) en un objeto de clase TStreamableTypes, que creara si fuera
necesario, y que sera asignado al puntero static identificado por types en
pstream. De esta forma se generara una base de datos conteniendo todas
las clases deseadas persistentes de una aplicacin.

FUNCION LECTORA

De igual forma a como hemos procedido con la funcin escritora debemos


implementar la funcin lectora, declarada en la seccin public de nuestra
clase:

void* MiClasePersistente::read( ipstream& ips ) { /* ... */ }

donde ips es una referencia a un objeto de clase opstream (input persistent


stream), derivada de pstream para operaciones de extraccin. La nica
restriccin en la definicin de tal funcin es la obligatoriedad de conservar el
orden de aparicin de los distintos elementos a ser ledos tal como fue
implementado en el cuerpo de la funcin escritora. Ello se debe,
evidentemente, al hecho que tales elementos son almacenados de forma
secuencial. Realmente la funcin lectora es una cuasi-repeticin formal de la
funcin escritora, sustituyendo opstream& por ipstream&, write por read, y
trocando el tipo de retorno void por void* (y aadiendo a tal efecto la
sentencia de retorno return this;).

Atisbamos, de esta forma, un primer procedimiento de mecanizacin


generativa de cdigo. En lugar de escribir repetitivamente dos funciones
completas, podramos definir en la clase una funcin de propsito general
que despus aplicaramos convenientemente en sus apropiadas funciones
escritora o lectora. Vemoslo:

class TStreamable {
protected:
void write( opstream& ops ) { persist( ops ); }
void* read( ipstream& ips ) { persist(ips);return this; }
virtual void persist( pstream& ps ) = 0;
// ...

C++/OOP: UN ENFOQUE PRCTICO Pgina 184/297


};

class MiNuevaClasePersistente:
public MiClaseBase, public TStreamable { /* ... */ };
void MiNuevaClasePersistente::persist( pstream& ps )
{
MiClaseBase::persist( ps );
ps[ objetoDeTipoIncorporado ];
ps[ objetoDeOtraClasePersistente ];
ps[ punteroAObjetoDeOtraClasePersistente ];
}

Notemos que se ha modificado la clase TStreamable, incorporando una


nueva funcin virtual pura (persist) de obligatoria redefinicin en sus clases
derivadas, ms la definicin basada en sta de las funciones read y write. Se
han trocado, tambin, estas ltimas funciones desde virtuales puras a
mtodos normales de la clase, siendo as normalmente heredadas, sin
necesidad de redefinicin, por las clases persistentes derivadas de TStreama-
ble. Necesitaramos, por otra parte, definir sobrecargas del operador '[]' en
las clases opstream e ipstream, representadas genricamente de la siguiente
forma:

class ipstream : virtual public pstream {


public:
pstream& operator [] ( tipo& objetoDeTipo)
{
return *this >> objetoDeTipo;
}
// ...
};

class opstream : virtual public pstream {


public:
pstream& operator [] ( tipo& objetoDeTipo )
{
return *this << objetoDeTipo;
}
// ...
};

Se procurar, as, una sobrecarga de tal operador para cada una de las
sobrecargas existentes en tales clases para los operadores de insercin y
extraccin. Estamos obviando, empero, (y de aqu el cuasi en la pretendida
repeticin formal de cdigo) la especial codificacin necesitada para el
archivo y recuperacin de objetos de tipo agregado-definido-por-el-usuario.
Ms adelante concretaremos en detalle esta idea.

En el acercamiento prctico a la persistencia influyen sobre todo las


consideraciones sobre la recuperacin de los objetos, constituyndose las
restricciones observadas en tal proceso en lmites del modelado de su
almacenamiento, que depender de aqul. Hemos repasado comprehen-

C++/OOP: UN ENFOQUE PRCTICO Pgina 185/297


sivamente el proceso de escritura de los datos internos de los objetos en
streams persistentes, pero qu ocurre con la identidad de los objetos tal y
como fue establecida en el principio del anexo? Antes de iniciar el volcado
de los datos miembro se significa en el pstream el identificador de la clase
(devuelto por la funcin StreamableName()) antecedido por un carcter '['
(tras el que, realmente, se escribe el valor de la longitud de la cadena,
moldeado como char). Tal sealizacin nos habr de servir para crear en
memoria los objetos de la clases especificadas, para despus proceder a la
copia recursiva desde el pstream de los datos miembro. Esto requiere un
detenido examen: veamos la representacin simblica de un pstream tras la
aplicacin, segn lo expuesto, de la funcin escritora a un objeto de clase
CID1:

[CID1itdv11|itdv12[CID2itdv21]itdv13[CID3]
[CID4itdv41itdv42itdv43]itdv14]

donde CIDn representa un identificador de clase devuelto por la funcin


StreamableName(), mientras que itdvnx equivale al valor de los datos de
tipo incorporado contenidos en cada uno de los objetos de tipo CIDn,
mientras que el par [] deviene en alfabeto de un lenguaje restringido de
Dyck. La reconstruccin de la estructura de objetos transcrita al pstream
aparece engaosamente transparente, como puede apreciarse en el siguiente
cdigo simblico:

CD1* punteroCD1 = new CD1;


punteroCD1->idt11 = idtv11;
punteroCD1->idt12 = idtv12;
CD2* punteroCD2 = new CD2;
punteroCD2->idt21 = idt22;
punteroCD1->CD2td = *punteroCD2;
punteroCD1->idt13 = idtv13;
CD3* punteroCD3 = new CD3;
punteroCD1->CD3td = *punteroCD3;
CD4* punteroCD4 = new CD4;
punteroCD4->itd41 = itdv41;
punteroCD4->itd42 = itdv42;
punteroCD4->itd43 = itdv43;
punteroCD1->CD4td = *punteroCD4;
punteroCD1->itd14 = itdv14;

Realmente es errnea esta sencilla codificacin? Bien: debemos notar, ante


todo, que la correspondencia entre cdigo y pstream es perfecta: tal cdigo
nos sirve nicamente para su aplicacin a ese nico pstream, por lo que
parece que deberamos asociar un bien determinado mtodo a cada
pstream. Pero esto es absurdo!: lo que deseamos implementar es la
capacidad general de recuperacin a memoria de los objetos almacenados
en un pstream con independencia de la identidad concreta de estos objetos.
Examinemos, por otro lado, el "inocente" cdigo:

CD3* punteroCD3 = new CD3;

C++/OOP: UN ENFOQUE PRCTICO Pgina 186/297


En l estamos utilizando el operador new para alojar en el rea de memoria
de almacenamiento libre un nuevo objeto del tipo CD3, inicializado
utilizando el constructor por defecto de la clase CD3. Imaginemos que en tal
constructor aparece el siguiente cdigo:

CD3::CD3() : MiWindowEstandar()
{
// inicializacin de variables diversas
iniciaDialogoModal();
}

As la invocacin del constructor resultara en la interrupcin de toda la


secuencia de lectura, al crear el objeto una ventana estndar e iniciar un
dilogo modal.

Analizando las trabas expuestas observamos que necesitamos, pues, un


cdigo general, que habr de ser usado por lo que Borland denomina
stream manager, y que nos permitir extraer de un pstream dado, cuyo
contenido es desconocido en tiempo de compilacin, informacin suficiente
para reconstruir un sistema interrelacionado de objetos con determinados
estados internos. De hecho, la definicin en cada clase de la funcin lectora
proporciona el ms adecuado mtodo de restauracin de los datos internos
de los objetos de tal clase, pero pinsese que C++ es un lenguaje de
jerarqua dual: las clases son entidades distintas de los objetos, de forma
que el mensaje de lectura tiene que ser dirigido al objeto, el cual no existe
antes de la aplicacin a la clase de un determinado constructor cuya
idoneidad desconocemos. Aclaremos la situacin: en primer lugar y antes de
extraer del pstream los valores de los datos miembros de un determinado
objeto debemos proceder a la creacin de ste, pero no tenemos forma de
saber qu constructor debemos utilizar a fin que no interfiera con la
secuencia de lectura o genere indeseables efectos laterales, como por
ejemplo el envo de mensajes que alteraran la estructura interna de objetos
ya reconstruidos. Necesitamos, pues, de un constructor especial para cada
clase que se limite a alocar el espacio libre necesario para alojar el esqueleto
y la tabla de funciones virtuales de un nuevo objeto de tal clase que, como
contenedor, ser oportunamente "rellenado" desde el pstream (recordemos
aqu que el tamao de un objeto no es igual a la suma del tamao de sus
miembros: los objetos de una clase vaca, por ejemplo, poseen un tamao
no-nulo). Tal constructor debe poseer caractersticas identificadoras nicas,
un cuerpo vaco y un sistema de control de la inicializacin de sus clases
base. Todo esto se cumple, en definitiva, con el constructor

class MiClasePersistente : public MiClaseBasePersistente,


virtual public TStreamable {
public:
MiClasePersistente( StreamableInit s );
// ...
};
MiClasePersistente::MiClasePersistente( StreamableInit s )

C++/OOP: UN ENFOQUE PRCTICO Pgina 187/297


: MiClaseBasePersistente( streamableInit ) {}

donde StreamableInit es un enum que consta del nico enumerador


streamableInit, declarado en <objstrm.h> (o en <ttypes.h> para TurboVi-
sion) de la forma

enum StreamableInit { streamableInit };

Vemos que la singularidad del enumerador asegura una cierta proteccin


contra el mal uso del constructor, a la vez que se explicita el uso de similar
constructor en las clases base a las que se habra aplicado el mismo
tratamiento de persistencia. Ntese que no es necesaria la inicializacin
expresa de TStreamable, pues es sta la nica clase que el stream manager
directamente conoce, asegurando que la aplicacin rutinaria del constructor
por defecto no vulnerar el proceso. Hay que destacar, no obstante, que si
la clase aspirante-a-persistente derivara, entre otras, de una o ms clases en
las que no hubiera sido implementado este tipo de persistencia, no sera
posible acceder a estos constructores especiales, y el orden de aplicacin de
constructores en derivacin podra generar el mismo problema que se
intentaba evitar: la intervencin incontrolada de un constructor
(normalmente por defecto) de una clase base en el proceso de creacin del
esqueleto del objeto. Notamos, entonces, que el mantenimiento de la
seguridad del proceso nos obligara a repetir o desplazar la derivacin de
persistencia hacia atrs en la escala jerrquica de la clase inicialmente
elegida. En realidad tal circunstancia refuerza la idoneidad, dada una
jerarqua de clases, de la aplicacin de la derivacin de TStreamable cuando
menos (y tal vez nicamente) a sus clases base.

Poseemos ya, segn lo visto, un adecuado esquema de constructores, pero


quin efectuar las correspondientes llamadas a los mismos? La respuesta
resulta en otra pregunta: quin conoce ms de un objeto que el objeto en
s? Efectivamente: la tarea de creacin de esqueletos de objetos debera ser
encomendada a uno de tales objetos. Debemos, pues, declarar en la seccin
public de nuestra clase una funcin constructora que podra ser definida as:

TStreamable* MiClasePersistente::build()
{
return new MiClasePersistente( streamableInit );
}

Pero, puede un objeto crear otro objeto u operar de alguna manera antes
de ser l mismo creado?. Evidentemente no. Necesitamos aqu, as, una
operacin de creacin no encapsulada en un objeto particular, pero
restringida al mbito de la descripcin de tipo del mismo: esto es, una
funcin miembro esttica. La funcin build() sera declarada, pues, como
static en el protocolo de descripcin de la clase afectada, siendo accedida a
travs de un puntero, accesible a su vez en la base de datos soporte del
registro de clases por medio del identificador devuelto por
streamableName(). Recapitulemos: como ya vimos cuando detallbamos la

C++/OOP: UN ENFOQUE PRCTICO Pgina 188/297


funcin escritora, es preciso aadir para cada una de las clases deseadas
persistentes una lnea de cdigo, creadora de un objeto de tipo
TStreamableClass, la cual registrar convenientemente la clase en un
contenedor especial. Vemoslo aplicado a nuestra clase ejemplo:

TStreamableClass RMiClasePersistente(
MiClasePersistente::name,
MiClasePersistente::build,
__DELTA( MiClasePersistente ) );

El prototipo del constructor de TStreamableClass es el siguiente:

public:TStreamableClass::TStreamableClass(
const char* identificadorDeClase,
TStreamable* ( _FAR *punteroAFuncionBuild )(),
int offsetTStreamable );

correspondiendo el tercer argumento al offset desde la base del objeto hasta


el inicio de la parte TStreamable del mismo adquirida por derivacin, cuyo
valor ser almacenado en un dato miembro de TStreamableClass llamado
delta. Est distancia ser convenientemente minorada de la direccin del
objeto implcitamente moldeado a TStreamable en su uso iterativo como
argumento de funciones llamadas desde su insercin o extraccin en un
objeto pstream. Este mecanismo permitir el registro de los objetos de la
clase encontrados en la secuencia lectora con su direccin correcta, y no con
la correspondiente a su parte TStreamable (recordemos que los objetos son
registrados como pares (direccion, indiceSecuencial)). En lugar de tener que
calcular tal valor para cada clase, Borland provee una macro, __DELTA-
(IdentificadorClase), que automticamente realiza este trabajo. En
definitiva, el cdigo anteriormente descrito crea un nuevo objeto de tipo
TStreamableClass, encapsulador de los valores asumidos por los argumentos
de su constructor, el cual a su vez registrar la clase en un objeto contene-
dor de tipo TStreamableTypes apuntado por pstream::types (si el puntero es
nulo, se crear un nuevo objeto contenedor y se asignara su direccin a
aqul). En tal contenedor se registrarn todas las clases de una aplicacin
mediante la insercin de los pares (identificadorClase, punteroAFuncion-
BuildDeLaClase) correspondientes a cada una de ellas.

Precisamente este mecanismo de registro obligar a explicitar el enlace con


clases cuya declaracin aparezca en mdulos distintos del dado. A tales
efectos Borland provee, igualmente, una macro cuyo cometido es
proporcionar una referencia al objeto de tipo TStreamableClass usado para
el registro de todas las clases de la aplicacin, con la sintaxis siguiente:

__link(RegIdentificadorDeMiClasePersistenteExternaAEsteModulo)

Retomemos, al fin, la implementacin en nuestra clase de la capacidad de


recuperacin de objetos a memoria desde un pstream. Como ya notamos,
debe dotarse a cada clase de una funcin lectora de la forma:

C++/OOP: UN ENFOQUE PRCTICO Pgina 189/297


void* MiClasePersistente::read( ipstream& ips )
{
MiClaseBase::read( ips );
ips >> objetosDeTipoIncorporado;
ips >> objetoDeOtraClasePersistente;
ips >> punteroAObjetoDeOtraClasePersistente;
}

estructural y secuencialmente paralela a la funcin escritora. Examinemos


seguidamente el proceso de extraccin de objetos significando mediante
pseudo-cdigo la secuencia lectora del pstream:

extraccinObjeto: lectura de la seal '[' de inicio de un objeto


lectura de la longidud (int) del identificador de clase del objeto
lectura del identificador de la clase MCP del objeto
bsqueda del identificador de clase en el registro
de clases persistentes (precondicin)
construccin de un nuevo objeto del tipo MCP usando el puntero a MCP::build
registro nuevo objeto MCP en objeto tipo TPReadObjects
llamada a la funcin apuntada por MCP::read
loop while existen lneas de cdigo en funcin MCP::read
[ insercin de un objeto de tipo incorporado ]*
lectura directa del valor de la variable desde el pstream
[ insercin de un objeto de tipo definido-por-el-usuario ]*
call extraccinObjeto
[ insercin de un puntero a un objeto de tipo definido-por-el-usuario ]*
select sealizador puntero
case pstream::Null:
asignacin de cero al puntero
case pstream::ptIndexed:
lectura ndice registro orden lectura objeto
bsqueda por ndice en registro objetos ledos
asignacin direccin objeto encontrado a puntero
case pstream::ptObject:
call extraccinObjeto
asignacin direccin nuevo objeto a puntero
endselect
endloop
terminaInsercinObjeto: lectura del sealizador final del objeto ']'

As como la funcin escritora usaba de un contenedor TPWrittenObjects para


el control de los objetos ya escritos, la funcin lectora usa de un parecido
objeto de tipo TPReadObjects, una clase derivada pblicamente de TNS-
Collection, que a su vez representa un contenedor no-ordenado de objetos
de clases no derivadas de TStreamable.. En el objeto de tipo TPReadObjects
se insertarn las direcciones en memoria de los objetos ya ledos, asimilando
cada una al ndice (base 1) secuencial de registro, equivalente al de escritura.
En correspondencia con el mecanismo escritor, el cdigo para el registro de
los objetos habr sido insertado en el cuerpo de la funcin void ip-
stream::readData(TStreamable&). Efectivamente el seguimiento de punteros
se ha traspasado al almacenamiento secundario en una separacin dual que,

C++/OOP: UN ENFOQUE PRCTICO Pgina 190/297


lgicamente, originar una penalizacin sobre el ms eficiente mecanismo
transparente de nivel singular.

Como vemos, el modelado de la funcin escritora sobre la base de los


procedimientos lectores origina una recproca correspondencia autoexpli-
cativa del proceso de extraccin con respecto al de insercin. Como vimos
anteriormente, esto nos induce a usar mecanismos ms generales, como el
expuesto mediante sobrecarga del operador '[]'. En aquel momento dejamos
pendiente la revisin de la generalizacin en la persistencia de ciertos
objetos de tipo agregado. Vemoslo ahora.

La aplicacin ms inmediata de lo expuesto tendra lugar en la jerarqua de


clases contenedoras notadas por Borland como "collections". Estas clases
llevan incoporado cdigo soporte para la persistencia expuesta, habiendo
solucionado el problema del almacenamiento y recuperacin de objetos con
un mecanismo de tipo-seguro que consiste en la implementacin en cada
clase de los mtodos writeItem() y readItem(), inicialmente definidos como
funciones virtuales puras en TCollection de la forma:

virtual void writeItem( void*, opstream& ) = 0;


virtual void* readItem( ipstream& ) = 0;

forzando su redefinicin en las clases de ella derivadas. Simplificando, este


mecanismo exigira, en una clase ejemplo Contenedora, la explicitacin de
tipo de los items de la siguiente manera:

void Contenedora::writeItem( void *prst, opstream& ops )


{
ops << (Contenedora*)prst;
}

void* Contenedora::readItem( ipstream& ips )


{
Contenedora *prst;
is >> prst;
return prst;
}

Una distinta aproximacin metodolgica a la construccin lectora expuesta


sera, en C++, la basada en el "Idioma de Ejemplares" detallado en
[Copli92], inmersa en las ms generales tcnicas de "constructores
virtuales". Tales tcnicas merecen, empero, captulo expositivo aparte.

C++/OOP: UN ENFOQUE PRCTICO Pgina 191/297


EJEMPLOS PRCTICOS

Borland ofrece una pedaggica ejemplificacin de la aplicacin de la


persistencia en una jerarqua de objetos "grficos" en el archivo de
demostracin tvguid021.cpp, incluido en TurboVision. A pesar que el cdigo
es extremadamente simple, el ejemplo no podra calificarse de muy
apropiado pues, tras su compilacin y enlace, el observador poco avezado,
atnito ante la repeticin de una composicin grfica esttica, podra
preguntarse para qu demonios sirve esto? Una aproximacin ms
interesante hubiera sido la implementacin de persistencia en, por ejemplo,
un objeto port adecuado a la client area de una determinada ventana, sobre
el que se utilizaran objetos lpices, brochas y rodillos, junto con colores,
fuentes tipogrficas, etc. mediante una transformacin, gestionada por un
objeto transformador xvirtual, desde coordenadas virtuales. Estaramos
operando, de hecho, con colores y formas en un mapa de bits. La
simplificada persistencia de ste causara que, en cualquier momento,
pudiramos "archivarlo" junto con las relaciones complejas que contuviera
(incluyendo el ltimo "dibujo" realizado) y fcilmente despus recuperarlo,
de tal forma que, en estructuras complejas inter-relacionadas como sta, la
secuencia concreta de transferencia entre el archivo y los objetos es
desconocida por el usuario o desarrollador.

La persistencia de objetos permite, entre otras aplicaciones, soportar el


almacenamiento expedito de imgenes, sonidos y, en general, composi-
ciones multimedia en ODBMSs.

PERSISTENCIA RECURSIVO-DERIVATIVA (RDP)

Como hemos visto, la implementacin en una clase dada del tipo de


persistencia expuesto obedece a la incorporacin en la descripcin de tal
clase de determinados datos y funciones miembros. En realidad, como ya
apuntamos, tal mecanismo es susceptible de ser mecanizado. Pero
enfoquemos el problema desde una ptica ms general: necesitaramos de
un esquema que permitiera la cualificacin o no de un objeto como
persistente en tiempo de ejecucin. Esto nos sugiere que debiera ser
aadido un filtro interruptor a cada una de las clases en una aplicacin, de
forma que soportaran mtodos para responder a este tipo de mensajes. Lo
ms evidente sera implementar la cualidad general de persistencia en todas
las clases y aadir a stas un miembro o flag que filtrara la accin del stream
manager.

Deben ser aadidas, antes de nada, las siguientes modificaciones a las clases
que se notan, sealadas en negrita:

class TStreamable {
friend class pstream;
private:
persist_mode persistStatus;

C++/OOP: UN ENFOQUE PRCTICO Pgina 192/297


protected:
TStreamable() {
persistMode = PERSISTENT;
}
enum persist_mode { TRANSIENT, PERSISTENT };
virtual void persist( pstream& ) = 0;
void* read( ipstream& ips ) { persist(ips);return this; }
void write( opstream& ops) { persist( ops ); }
public:
boolean isTransient() { return persistStatus == TRANSIENT; }
void setPersistMode( persist_mode pmode ) {
persistStatus = pmode; }
// ...
};

class pstream {
public:
virtual pstream& operator [] ( signed char& ) = 0;
virtual pstream& operator [] ( unsigned char& ) = 0;
virtual pstream& operator [] ( signed short& ) = 0;
virtual pstream& operator [] ( unsigned short& ) = 0;
virtual pstream& operator [] ( signed int& ) = 0;
virtual pstream& operator [] ( unsigned int& ) = 0;
virtual pstream& operator [] ( signed long& ) = 0;
virtual pstream& operator [] ( unsigned long& ) = 0;
virtual pstream& operator [] ( float& ) = 0;
virtual pstream& operator [] ( double& ) = 0;
virtual pstream& operator [] ( long double& ) = 0;
virtual pstream& operator [] ( TStreamable& ) = 0;
virtual pstream& operator [] ( void* ) = 0;
// ...
};

class ipstream : virtual public pstream {


public:
pstream& operator [] ( signed char& sch ) {
return *this >> sch;
}
pstream& operator [] ( unsigned char& uch ) {
return *this >> uch;
}
pstream& operator [] ( signed short& ssh ) {
return *this >> ssh;
}
pstream& operator [] ( unsigned short& ush ) {
return *this >> ush;
}
pstream& operator [] ( signed int& sint ) {
return *this >> sint;
}
pstream& operator [] ( unsigned int& uint ) {
return *this >> uint;
}
pstream& operator [] ( signed long& slng ) {

C++/OOP: UN ENFOQUE PRCTICO Pgina 193/297


return *this >> slng;
}
pstream& operator [] ( unsigned long& ulng ) {
return *this >> ulng;
}
pstream& operator [] ( float& fl ) {
return *this >> fl;
}
pstream& operator [] ( double& dbl ) {
return *this >> dbl;
}
pstream& operator [] ( long double& ldbl ) {
return *this >> ldbl;
}
pstream& operator [] ( TStreamable& prst ) {
return *this >> prst;
}
pstream& operator [] ( void* prst) {
return *this >> prst;
}
// ...
};

class opstream : virtual public pstream {


public:
pstream& operator [] ( signed char& sch ) {
return *this << sch;
}
pstream& operator [] ( unsigned char& uch ) {
return *this << uch;
}
pstream& operator [] ( signed short& ssh ) {
return *this << ssh;
}
pstream& operator [] ( unsigned short& ush ) {
return *this << ush;
}
pstream& operator [] ( signed int& sint ) {
return *this << sint;
}
pstream& operator [] ( unsigned int& uint ) {
return *this << uint;
}
pstream& operator [] ( signed long& slng ) {
return *this << slng;
}
pstream& operator [] ( unsigned long& ulng ) {
return *this << ulng;
}
pstream& operator [] ( float& fl ) {
return *this << fl;
}
pstream& operator [] ( double& dbl ) {
return *this << dbl;

C++/OOP: UN ENFOQUE PRCTICO Pgina 194/297


}
pstream& operator [] ( long double& ldbl ) {
return *this << ldbl;
}
pstream& operator [] ( TStreamable& prst ) {
return *this << prst;
}
pstream& operator [] ( TStreamable* prst) {
if ( prst->isTransient() )
prst = TRANSIENT;
return *this << prst;
}
// ...
};

Nuestra "propuesta" se basar directamente en el esquema desarrollado por


Borland, con las ventajas e inconvenientes que esto supone: ventajas como
la disponibilidad inmediata del soporte estructural o el bien-testeado
mecanismo funcional; inconvenientes como la penalizacin en tiempo de
ejecucin resultante del nivel adicional de indireccin, o la menor flexibilidad
resultante de la dependencia de un cdigo dado.

Necesitaremos de un Metasistema que nos permita acoplar la recursividad de


acceso sobre cada uno de los miembros de una clase, a la vez que "instale" y
"mantenga" las caractersticas de persistencia de las clases.

Entre los diferentes sistemas a elegir, quizs el ms evidente sea el que


usara de un preprocesador, aplicable como filtro sobre todas y cada una de
las clases intervinientes en la aplicacin deseada.

El preprocesador actuara sobre el cdigo original que a continuacin se


detalla:

class NombreClase [: [[cualificador]claseBase]+] {


[cualificador:]*
// datos miembros
tipoIncorporado tipoBasico;
NombreClase1 objeto;
NombreClase2* punteroAObjeto;
tipoIncorporado arrayDeTiposBasicos[ max1 ];
NombreClase2 arrayDeObjetos[ max2 ][ max3 ]
// funciones miembro
[valorRetorno funcionMiembro(argumentos);]*
};

produciendo la siguientes modificaciones, sealadas en negrita:

#ifdef PERSIST

#ifndef PERSIST_FLAG
#define PERSIST_FLAG
# define Uses_pstream

C++/OOP: UN ENFOQUE PRCTICO Pgina 195/297


# define Uses_ofpstream
# define Uses_ifpstream
# define Uses_TStreamableClass
# include <tv.h>
#endif /* PERSIST_FLAG */

__link( RegClaseBase )
__link( RegNombreClase1 )
__link( RegNombreClase2 )
__link( RegNombreClase3 )

class NombreClase : public TStreamable


[,[[cualificador]ClaseBase]+] {
#else /* PERSIST */
class NombreClase [: [[cualificador]claseBase]+] {
#endif /* PERSIST */
[cualificador:]*
// datos miembros invariantes
// funciones miembro invariantes
#ifdef PERSIST
private:
int indiceImpulsion;
virtual const char* streamableName() const {
return name; }
protected:
NombreClase( StreamableInit ) :
ClaseBase( streamableInit ) {}
void persist( pstream& );
public:
static const char* const name = "NombreClase";
static TStreamable* build() {
return new NombreClase( streamableInit ); }
#endif /* PERSIST */
};

#ifdef PERSIST
inline ipstream& operator >> ( ipstream& ips, NombreClase& nc )
{ return ips >> (TStreamable&)nc; }
inline ipstream& operator >> ( ipstream& ips, NombreClase*& nc )
{ return ips >> (void *&)nc; }
inline opstream& operator << ( opstream& ops, NombreClase& nc )
{ return ops << (TStreamable&)nc; }
inline opstream& operator << ( opstream& ops,NombreClase*& nc )
{ return ops << (void *&)nc; }

void NombreClase::persist( pstream& ps )


{
int indice[ 2 ]; // n mximo de dimensiones en arrays
ClaseBase::persist( ps );
ps[ tipoBasico ];
ps[ objeto ];
ps[ punteroAObjeto ];
ps[ referenciaAObjeto ];
for ( indice = 0; indice <= max1; indice++ )

C++/OOP: UN ENFOQUE PRCTICO Pgina 196/297


ps[ arrayDeTiposBasicos[ indice[0] ] ];
for ( indice[0] = 0; indice[0] <= max2; indice[0]++ )
for (indice[1] = 0; indice[1] <= max3; indice[1]++)
ps[arrayDeObjetos[ indice[0] ][ indice[1] ]];
}

TStreamableClass RNombreClase ( RNombreClase::name,


RNombreClase::build,
__DELTA( RNombreClase ) );

#endif /* PERSIST */

Tal preprocesador -al que en adelante denominaremos CP5 (C Plus Plus Persistence Pre-Processor)- deber
registrar, adicionalmente, la descripcin de cada clase en una database aparte. Para qu? Bien: imaginemos que
la clase de un determinado objeto sufre una variacin tras el archivo en un pstream del mismo. CP5 habr de ser
instruido para comparar cada clase con la descripcin registrada de la misma, y si aprecia algn cambio con
respecto a sta, buscar en el cdigo una funcin constructo-conversora del tipo:

NuevaClaseModificada( AntiguaDescripcionClase
objetoAntiguaClase );

la cual, bsicamente, explicitara la transferencia de identidades desde una descripcin a otra (la no existencia
expresa de tal mtodo obligara a CP5 a suplir un conversor por defecto, no siempre apropiado). Tal conversor
podra ser utilizado bien para la actualizacin de los pstreams (mediante la compilacin temporal de ambas
descripciones) bien para su inclusin en un sistema de versioning de clases, similar al usado en ODBMSs.

La construccin en C++ del preprocesador CP5, usando quiz de la tcnica llamada de "constuctores virtuales"
notada en [Copli92] y [Eckel92], excede el objetivo marcado en la redaccin de este anexo, cual inicialmente fue
la exposicin, a un nivel intermedio, de determinadas tcnicas no complejas de implementacin de la persistencia
en sistemas de objetos. La codificacin de CP5 pudiera, empero, merecer por su inters (pinsese en
analizadores lxicos, scanners, etc.) un prximo trabajo detallado.

LA EVOLUCIN DE LA PERSISTENCIA

Bien, lo que aqu mayormente se ha detallado es el esquema de persistencia de un compilador concreto: Borland
C++ 3.1. Pero, se trata de una estructuracin inamovible? Ciertamente no. En la versin 4.0 de este
compilador, una decisin de diseo, cual es el cambio de las libreras de clases de contenedores desde una
jerarqua csmica (tipo Smalltalk) a una parametrizacin completa mediante plantillas, repercute grandemente
sobre la implementacin de la persistencia, aunque conserva en buena medida su interfaz. As, por ejemplo,
desaparecen las sobrecargas de los operadores:

opstream& operator << ( opstream&, TStreamable* );


ipstream& operator >> ( ipstream&, void* & );

a la vez que se solventa la cualificacin de clases persistentes mediante el uso de las macros
DECLARE_STREAMABLE e IMPLEMENT_STREAMABLEX, donde X es un nmero que depende de las clases bases
inmediatas y de las clases bases virtuales de que deriva nuestra clase candidata. De alguna manera este cambio
en la implementacin afecta de manera menor a nuestro cdigo. Bien: esto es OOP. Hay que sealar, por ltimo,
que el esquema detallado en el presente anexo es, con las lgicas variaciones, el ms utilizado en las libreras
comerciales de clases.

C++/OOP: UN ENFOQUE PRCTICO Pgina 197/297


REFERENCIAS

[Atwoo91] Thomas Atwood, At last! A distributed database for Windows 3.0, Object Magazine, 1(1),
1991.

[Booch92] Grady Booch & Michael Vilot, Physical design: persistence, C++ Report, 4(3), 1992.

[Borla91] Borland International Inc, Borland C++ 3.1 Programmer's Guide, TurboVision for C++, ObjectWindows
for C++, 1992.

[Camm91] Stephanie J. Cammarata & Christopher Burdof, PSE: and object-oriented simulation
environment supporting persistence, Journal of Object-Oriented Programming, 4(6), 1991.

[Devis94] Ricardo Devis Botella, OOA/OOD : Un Enfoque Prctico, En preparacin.

[Copli92] James O. Coplien, Advanced C++ Programming Styles and Idioms, Addison-Wesley, 1992.

[Eckel92] Bruce Eckel, Virtual constructors, C++ Report, 4(3), 1992.

[Gorle90] Keith Gorlen, S. Orlow & P. Plexico, Data Abstraction and Object-Oriented Programming in C++, John
Wiley, New York, 1990.

[Meyer88] Bertrand Meyer, Object-Oriented Software Construction, Prentice-Hall, 1988.

[Rumba91] James Rumbaugh, Michael Blaha, William Premerlani, Frederick Eddy & William Lorensen,
Object-Oriented Modeling and Design, Prentice-Hall, 1991.

[Urloc91] Zack Urlocker, From applications to frameworks, Hotline on Object-Oriented Technology, 2(11), 1991.

C++/OOP: UN ENFOQUE PRCTICO Pgina 198/297

También podría gustarte