17.05.2013 Views

Clases y manejo de memoria dinámica en C++ - Universidad de ...

Clases y manejo de memoria dinámica en C++ - Universidad de ...

Clases y manejo de memoria dinámica en C++ - Universidad de ...

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 1 -<br />

<strong>Clases</strong> y <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong><br />

Repaso a las funciones <strong>de</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> C ............................................................................1<br />

Función calloc()...................................................................................................................................................1<br />

Función malloc() .................................................................................................................................................2<br />

Función realloc() .................................................................................................................................................2<br />

Función free() ......................................................................................................................................................3<br />

El operador new fr<strong>en</strong>te a malloc ............................................................................................................................3<br />

El operador <strong>de</strong>lete fr<strong>en</strong>te a free..............................................................................................................................8<br />

Sobrecarga <strong>de</strong> new y <strong>de</strong> <strong>de</strong>lete................................................................................................................................9<br />

Sobrecarga <strong>de</strong> new y <strong>de</strong>lete respecto a una clase específica.............................................................................11<br />

<strong>Clases</strong> con punteros miembros .............................................................................................................................14<br />

El Puntero this ......................................................................................................................................................22<br />

Funciones estáticas y el puntero this.................................................................................................................24<br />

Paso y retorno <strong>de</strong> objetos......................................................................................................................................24<br />

Paso y retorno <strong>de</strong> refer<strong>en</strong>cias a objetos................................................................................................................26<br />

Ejercicios propuestos ............................................................................................................................................27<br />

Repaso a las funciones <strong>de</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> C<br />

Función calloc()<br />

Prototipo: void *calloc(int num, int tam);<br />

Archivo cabecera: stdlib.h<br />

Valor <strong>de</strong>vuelto: Devuelve un puntero al primer byte <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> reservada, o un<br />

puntero<br />

NULL <strong>en</strong> el caso <strong>de</strong> no haberse podido reservar el bloque <strong>de</strong> <strong>memoria</strong> solicitado<br />

Finalidad: Reserva un bloque <strong>de</strong> <strong>memoria</strong> para almac<strong>en</strong>ar num elem<strong>en</strong>tos <strong>de</strong> tam bytes cada<br />

uno <strong>de</strong> ellos. Todo el bloque <strong>de</strong> <strong>memoria</strong> queda iniciado a 0.<br />

• num: indica el número <strong>de</strong> elem<strong>en</strong>tos con la misma estructura que ocuparán el bloque <strong>de</strong> <strong>memoria</strong> reservado.<br />

• tam: indica el tamaño <strong>en</strong> bytes <strong>de</strong> cada uno <strong>de</strong> los elem<strong>en</strong>tos que van a ocupar el bloque <strong>de</strong> <strong>memoria</strong><br />

reservada.<br />

La cantidad <strong>de</strong> <strong>memoria</strong> reservada vi<strong>en</strong>e <strong>de</strong>terminada por el resultado que se obti<strong>en</strong>e al multiplicar el número <strong>de</strong><br />

elem<strong>en</strong>tos a almac<strong>en</strong>ar <strong>en</strong> el bloque <strong>de</strong> <strong>memoria</strong> por el tamaño <strong>en</strong> bytes <strong>de</strong> cada uno <strong>de</strong> esos elem<strong>en</strong>tos, es <strong>de</strong>cir,<br />

num * tam.<br />

El uso <strong>de</strong> esta función se pue<strong>de</strong> ilustrar con el sigui<strong>en</strong>te ejemplo:<br />

int n=100, *pt;<br />

...<br />

pt = ( int * ) calloc (n, sizeof(int));<br />

En este ejemplo la función calloc reserva un bloque <strong>de</strong> <strong>memoria</strong> <strong>de</strong> n <strong>en</strong>teros, y retorna un puntero void a dicho<br />

bloque, o NULL si n fuera 0 o se produjera un error al no po<strong>de</strong>r reservar sufici<strong>en</strong>te espacio para reservar la<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 2 -<br />

<strong>memoria</strong> solicitada. Sobre el puntero que se retorna se <strong>de</strong>be hacer una operación <strong>de</strong> cast apropiada, para<br />

asignarlo al puntero <strong>de</strong>l tipo que se está tratando.<br />

Función malloc()<br />

Prototipo: void *malloc(int tam);<br />

Archivo cabecera: stdlib.h<br />

Valor <strong>de</strong>vuelto: Devuelve un puntero al primer byte <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> reservada, o un<br />

puntero<br />

NULL <strong>en</strong> el caso <strong>de</strong> no haberse podido reservar el bloque <strong>de</strong> <strong>memoria</strong> solicitado<br />

Finalidad: Reserva un bloque <strong>de</strong> <strong>memoria</strong> <strong>de</strong> tam bytes.<br />

• tam: indica el tamaño <strong>en</strong> bytes <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> que se <strong>de</strong>sea reservar.<br />

La cantidad <strong>de</strong> <strong>memoria</strong> reservada será <strong>de</strong> tam bytes.<br />

El uso <strong>de</strong> esta función se pue<strong>de</strong> ilustrar con el sigui<strong>en</strong>te ejemplo:<br />

struct Registro *ptr_reg;<br />

ptr_reg = ( struct Registro * ) malloc (sizeof (struct Registro));<br />

La función malloc reserva un bloque <strong>de</strong> <strong>memoria</strong> lo sufici<strong>en</strong>tem<strong>en</strong>te gran<strong>de</strong> como para acoger por completo al<br />

tipo <strong>de</strong> dato, y retorna un puntero void a dicho bloque, o NULL si el tamaño <strong>de</strong>l argum<strong>en</strong>to es 0 o se ha<br />

producido un error al no po<strong>de</strong>r reservar sufici<strong>en</strong>te espacio para alojar al tipo <strong>de</strong> dato. Sobre el puntero que se<br />

retorna se <strong>de</strong>be hacer una operación <strong>de</strong> cast apropiada, para asignarlo al puntero <strong>de</strong>l tipo que se está tratando.<br />

Función realloc()<br />

Prototipo: void *realloc(void *ptr, int nuevo_tamaño);<br />

Archivo cabecera: stdlib.h<br />

Valor <strong>de</strong>vuelto: Devuelve un puntero al primer byte <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> reservada, o un<br />

puntero<br />

NULL <strong>en</strong> el caso <strong>de</strong> no haberse podido reservar el bloque <strong>de</strong> <strong>memoria</strong> solicitado<br />

Finalidad: Cambia el tamaño <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> apuntada por ptr al nuevo tamaño<br />

indicado por nuevo_tamaño<br />

• ptr: puntero que apunta al bloque <strong>de</strong> <strong>memoria</strong> reservado.<br />

• nuevo_tamaño: valor <strong>en</strong> bytes que indica el nuevo tamaño <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> apuntado por ptr y que<br />

pue<strong>de</strong> ser mayor o m<strong>en</strong>or que el original.<br />

En estas tres funciones es importante comprobar que el puntero <strong>de</strong>vuelto por ellas no es un puntero nulo<br />

(NULL) antes <strong>de</strong> hacer uso <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> reservado.<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 3 -<br />

Función free()<br />

Prototipo: void free(void *ptr);<br />

Archivo cabecera: stdlib.h<br />

Valor <strong>de</strong>vuelto: Ninguno<br />

Finalidad: Libera el bloque <strong>de</strong> <strong>memoria</strong> apuntada por ptr y que previam<strong>en</strong>te ha sido asignado<br />

mediante malloc() o calloc().<br />

• ptr: variable puntero que <strong>de</strong>be cont<strong>en</strong>er la dirección <strong>de</strong> <strong>memoria</strong> <strong>de</strong>l primer byte <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> que<br />

<strong>de</strong>seamos liberar. En caso <strong>de</strong> que no sea un puntero previam<strong>en</strong>te reservado por malloc() o calloc(), la<br />

llamada a la función free() pue<strong>de</strong> ocasionar una parada o interrupción <strong>de</strong>l sistema.<br />

El operador new fr<strong>en</strong>te a malloc<br />

En el l<strong>en</strong>guaje C, la región <strong>de</strong> <strong>memoria</strong> que está disponible a la hora <strong>de</strong> la ejecución recibe el nombre <strong>de</strong> heap.<br />

En <strong>C++</strong> el espacio <strong>de</strong> <strong>memoria</strong> disponible se conoce como almac<strong>en</strong>ami<strong>en</strong>to libre 1 . La difer<strong>en</strong>cia <strong>en</strong>tre los dos se<br />

<strong>en</strong>cu<strong>en</strong>tra <strong>en</strong> las funciones que se utilic<strong>en</strong> para el acce<strong>de</strong>r a esta <strong>memoria</strong>.<br />

El método para solicitar <strong>memoria</strong> <strong>de</strong>l heap <strong>en</strong> C se basa <strong>en</strong> el uso <strong>de</strong> la función malloc y sus <strong>de</strong>rivadas (calloc,<br />

realloc, ...). En <strong>C++</strong> no es correcto utilizar malloc para alojar <strong>de</strong> forma <strong>dinámica</strong> una nueva instancia <strong>de</strong> una<br />

clase. La razón es que si se usa malloc, se pier<strong>de</strong>n las v<strong>en</strong>tajas que nos ofrec<strong>en</strong> los constructores, ya que se llama<br />

al constructor <strong>de</strong> la clase cada vez que se crea un objeto. Si se usa malloc para crear un objeto, se ti<strong>en</strong>e un<br />

puntero a un bloque <strong>de</strong> <strong>memoria</strong> sin iniciar. Entonces se pue<strong>de</strong>n hacer llamadas a métodos con objetos que no<br />

están bi<strong>en</strong> construidos, y que por lo tanto van a dar resultados erróneos.<br />

La técnica más a<strong>de</strong>cuada es utilizar la alternativa que ofrece <strong>C++</strong> mediante el operador new.<br />

El operador new conoce la clase <strong>de</strong>l objeto, o el tipo <strong>de</strong> la variable, que se quiere alojar <strong>en</strong> el almac<strong>en</strong>ami<strong>en</strong>to<br />

libre. De esta forma este operador llama automáticam<strong>en</strong>te al constructor que corresponda, <strong>de</strong>p<strong>en</strong>di<strong>en</strong>do <strong>de</strong> los<br />

argum<strong>en</strong>tos que se especifiqu<strong>en</strong>, para iniciar la <strong>memoria</strong> que ha reservado.<br />

La forma <strong>de</strong> utilizar este operador es la sigui<strong>en</strong>te:<br />

= new [];<br />

De la sintaxis <strong>de</strong> new se pue<strong>de</strong> <strong>de</strong>ducir que no es una función, y que por lo tanto no ti<strong>en</strong>e una lista <strong>de</strong><br />

argum<strong>en</strong>tos <strong>en</strong>tre paréntesis. Es un operador que se aplica al nombre <strong>de</strong>l tipo, pero no se ti<strong>en</strong>e que realizar un<br />

cast 2 ni utilizar el operador sizeof para <strong>de</strong>terminar su tamaño, porque new conoce el tipo <strong>de</strong> dato y su tamaño.<br />

Si new no <strong>en</strong>cu<strong>en</strong>tra espacio para alojar lo que se le pi<strong>de</strong>, <strong>de</strong>vuelve 0 3 .<br />

El operador new reemplaza a las funciones malloc y calloc <strong>de</strong> la biblioteca estándar <strong>de</strong> C, pero no sustituye a<br />

realloc. La función realloc pue<strong>de</strong> ser simulada, pero la aproximación que se haga supondrá siempre un aum<strong>en</strong>to<br />

1<br />

Free store.<br />

2<br />

El compilador comprueba que el tipo <strong>de</strong>l puntero que <strong>de</strong>vuelve new y el tipo <strong>de</strong>l puntero al que se asigna son<br />

<strong>de</strong>l mismo tipo. En caso contrario g<strong>en</strong>era un error.<br />

3<br />

En <strong>C++</strong> un puntero nulo vale 0 <strong>en</strong> lugar <strong>de</strong> NULL.<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 4 -<br />

<strong>de</strong> las líneas <strong>de</strong>l código fu<strong>en</strong>te, así como conocer el tamaño original <strong>de</strong>l espacio reservado mediante el puntero<br />

que se va cambiar. Pero afortunadam<strong>en</strong>te se pue<strong>de</strong> utilizar g<strong>en</strong>eralm<strong>en</strong>te realloc para cambiar el tamaño <strong>de</strong>l<br />

espacio reservado con new.<br />

Como se ha dicho, con new se pue<strong>de</strong> reservar espacio para cualquier tipo <strong>de</strong> dato, si<strong>en</strong>do muy utilizado para<br />

reservar espacio para matrices 4 . Un ejemplo:<br />

float *c<strong>en</strong>tros;<br />

c<strong>en</strong>tros = new float[numero];<br />

Para ilustrar todo lo visto hasta el mom<strong>en</strong>to <strong>de</strong>l operador new se ha realizado este s<strong>en</strong>cillo programa <strong>de</strong> ejemplo:<br />

// Programa: malloc versus new<br />

// Fichero: MALVNEW.CPP<br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

class Prueba {<br />

int i, j;<br />

char *s;<br />

public:<br />

Prueba() { // Constructor por <strong>de</strong>fecto<br />

i=j=5;<br />

s=new char[5];<br />

strncpy(s, "Hola", 5);<br />

}<br />

Prueba(char *cad, int a, int b) { // Constructor con parámetros<br />

i=a; j=b;<br />

s=new char[strl<strong>en</strong>(cad)+1];<br />

strcpy(s, cad);<br />

}<br />

~Prueba () {<strong>de</strong>lete [] s;} // Destructor<br />

int miembro(void) { // Función para mostrar los datos <strong>de</strong> la clase<br />

cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 5 -<br />

void main (void)<br />

{<br />

// Creamos un objeto dinámico con malloc<br />

Prueba *P1;<br />

P1=(Prueba *) malloc (sizeof (Prueba));<br />

// Y nos vamos a estrellar: Sal<strong>en</strong> caracteres raros por pantalla e<br />

// incluso se pue<strong>de</strong> colgar el programa<br />

cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 6 -<br />

<strong>C++</strong> <strong>de</strong>fine un puntero a función, <strong>de</strong> forma que cuando ocurre un error, la función a la que apunta el puntero es<br />

llamada. La <strong>de</strong>finición <strong>de</strong> dicho puntero es:<br />

void (* _new_handler)();<br />

El uso <strong>de</strong> este puntero ti<strong>en</strong>e el inconv<strong>en</strong>i<strong>en</strong>te <strong>de</strong> no ser estándar. Por ello se recomi<strong>en</strong>da el uso <strong>de</strong> una función <strong>de</strong><br />

biblioteca <strong>de</strong>finida <strong>en</strong> new.h, y que se llama set_new_handler, que toma como argum<strong>en</strong>to un puntero a función,<br />

y asigna la función al puntero _new_handler. Aunque este segundo método es más común, y por lo tanto más<br />

conv<strong>en</strong>i<strong>en</strong>te, tampoco es estándar 7 .<br />

// Programa: Manejo <strong>de</strong> errores <strong>en</strong> la reserva <strong>de</strong> espacio con new<br />

// Versión Borland <strong>C++</strong><br />

// Fichero: NEW_ERR.CPP<br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

#<strong>de</strong>fine PASO 20000<br />

void MemoriaInsufici<strong>en</strong>te(void)<br />

{<br />

cerr


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 7 -<br />

// Programa: Manejo <strong>de</strong> errores <strong>en</strong> la reserva <strong>de</strong> espacio con new<br />

// Versión Microsoft <strong>C++</strong><br />

// Fichero: NEW_ERR.CPP<br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

int MemoriaInsufici<strong>en</strong>te(size_t size)<br />

{<br />

cerr


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 8 -<br />

El operador <strong>de</strong>lete fr<strong>en</strong>te a free<br />

El operador <strong>de</strong>lete se pue<strong>de</strong> <strong>de</strong>cir que es para new, lo que free para malloc. Esto es, libera los bloques <strong>de</strong><br />

<strong>memoria</strong>, <strong>de</strong>jándolos listos para futuras reservas.<br />

La sintaxis <strong>de</strong> <strong>de</strong>lete es muy s<strong>en</strong>cilla:<br />

<strong>de</strong>lete ;<br />

Cuando se trata <strong>de</strong> liberar el espacio asociado un puntero a un objeto, <strong>de</strong>lete <strong>de</strong> forma automática llama al<br />

<strong>de</strong>structor.<br />

El operador <strong>de</strong>lete se <strong>de</strong>be utilizar sólo con punteros que retorne new. Mi<strong>en</strong>tras que la función free se <strong>de</strong>be usar<br />

con punteros que apunt<strong>en</strong> a zonas <strong>de</strong> <strong>memoria</strong> reservadas mediante funciones <strong>de</strong>rivadas <strong>de</strong> malloc. De no<br />

hacerse así, y <strong>de</strong>p<strong>en</strong>di<strong>en</strong>do <strong>de</strong> los compiladores, pue<strong>de</strong> causar graves problemas 9 . Si se int<strong>en</strong>ta liberar dos veces<br />

un mismo puntero, los resultados pue<strong>de</strong>n ser catastróficos. Estas dos circunstancias <strong>de</strong>b<strong>en</strong> ser controladas por el<br />

programador, ya que el compilador no las <strong>de</strong>tecta.<br />

Se pue<strong>de</strong> liberar un puntero nulo sin consecu<strong>en</strong>cias adversas.<br />

El uso <strong>de</strong> <strong>de</strong>lete con los tipos preestablecidos no ti<strong>en</strong>e misterios, salvo <strong>en</strong> el caso <strong>de</strong> las matrices, que se <strong>de</strong>be<br />

usar la sigui<strong>en</strong>te sintaxis:<br />

<strong>de</strong>lete [tamaño] ;<br />

En la mayoría <strong>de</strong> los compiladores basta con poner los corchetes vacíos, ya que ignoran el número que se ponga<br />

<strong>en</strong>tre ellos.<br />

Un s<strong>en</strong>cillo ejemplo <strong>de</strong>l uso <strong>de</strong>l operador unario <strong>de</strong>lete pue<strong>de</strong> ser el sigui<strong>en</strong>te:<br />

// Programa: Uso <strong>de</strong> <strong>de</strong>lete<br />

// Fichero: DELETE1.CPP<br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

void main (void)<br />

{<br />

int *i, *vi;<br />

}<br />

i=new int;<br />

*i=6;<br />

vi= new int[2];<br />

vi[0]=0; vi[1]=1;<br />

cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 9 -<br />

Sobrecarga <strong>de</strong> new y <strong>de</strong> <strong>de</strong>lete<br />

En <strong>C++</strong> es posible re<strong>de</strong>finir los operadores new y <strong>de</strong>lete sobrecargándolos, si se quiere construir un<br />

procedimi<strong>en</strong>to <strong>de</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> personalizado.<br />

Los esqueletos <strong>de</strong> las funciones que sobrecargan estos operadores son:<br />

void *operator new (size_t tamaño)<br />

{<br />

// Reserva <strong>dinámica</strong><br />

return puntero_void;<br />

}<br />

void operator <strong>de</strong>lete (void puntero)<br />

{<br />

// Liberar <strong>memoria</strong> a la que apunta el puntero<br />

}<br />

El operador new toma como argum<strong>en</strong>to un tipo size_t que es un tipo <strong>en</strong>tero que pue<strong>de</strong> cont<strong>en</strong>er el mayor bloque<br />

individual <strong>de</strong> <strong>memoria</strong> que se pueda reservar. El número <strong>de</strong> bytes a reservar se indican mediante el parámetro<br />

tamaño. A<strong>de</strong>más cualquier operador new que se cree <strong>de</strong>be <strong>de</strong>volver un puntero void.<br />

En cuanto al operador <strong>de</strong>lete toma como argum<strong>en</strong>to un puntero <strong>de</strong> tipo void, el cual apunta a la zona <strong>de</strong><br />

<strong>memoria</strong> que <strong>de</strong>be liberar. A<strong>de</strong>más cualquier operador <strong>de</strong>lete que sea creado por el programador no <strong>de</strong>be<br />

retornar nada.<br />

Si se re<strong>de</strong>fine el operador new <strong>de</strong> esta forma se sigue llamando al constructor <strong>de</strong> la clase cuando se ti<strong>en</strong>e una<br />

instancia <strong>dinámica</strong> <strong>de</strong> una clase.<br />

Sin embargo, pue<strong>de</strong> surgir algún tipo <strong>de</strong> problemas, cuando se sobrecargan estos operadores, con el uso <strong>de</strong><br />

realloc <strong>de</strong>bido a que el nuevo sistema <strong>de</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> utilice las mismas técnicas <strong>de</strong> <strong>manejo</strong> <strong>de</strong><br />

<strong>memoria</strong> que las funciones <strong>de</strong> biblioteca <strong>de</strong>l C.<br />

Para ilustrar la sobrecarga <strong>de</strong> los operadores new y <strong>de</strong>lete se va ha realizar un s<strong>en</strong>cillo programa que inicie el<br />

cont<strong>en</strong>ido <strong>de</strong>l bloque <strong>de</strong> <strong>memoria</strong> que se quiere reservar a 0 antes <strong>de</strong> utilizarlo 10 .<br />

// Programa: Sobrecarga <strong>de</strong> los operadores new y <strong>de</strong>lete<br />

// Fichero: N&DOVER1.CPP<br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

void *operator new ( size_t s ) {<br />

void *ptr=calloc(1, s);<br />

return ptr;<br />

}<br />

void operator <strong>de</strong>lete ( void * ptr ) {<br />

free ( ptr );<br />

}<br />

10 Se utilizará la función calloc porque ésta inicia a 0 todo el bloque <strong>de</strong> <strong>memoria</strong> que reserva.<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 10 -<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 11 -<br />

void main(void) {<br />

float *p=new float[5];<br />

for (register int i=0; i


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 12 -<br />

Cuando se va a procesar el operador new para una instancia <strong>de</strong> una clase, el compilador primero comprueba si<br />

están <strong>de</strong>finidos para dicha clase, y <strong>en</strong> caso afirmativo utiliza las versiones específicas <strong>de</strong> la clase. En caso<br />

contrario, si han sido sobrecargados globalm<strong>en</strong>te, emplea estas nuevas versiones globales, y <strong>en</strong> último caso<br />

utilizaría los operadores estándar new y <strong>de</strong>lete.<br />

Para ilustrar la sobrecarga <strong>de</strong> estos operadores respecto <strong>de</strong> una clase, se pres<strong>en</strong>ta el sigui<strong>en</strong>te ejemplo:<br />

// Programa: Sobrecarga <strong>de</strong> los operadores new y <strong>de</strong>lete respecto <strong>de</strong> una<br />

// clase<br />

// Fichero: N&DOVER3.CPP<br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

#<strong>de</strong>fine NOMBRES 10<br />

class Nombre {<br />

char nombre[25];<br />

public:<br />

Nombre ( const char * );<br />

void Pres<strong>en</strong>ta ( void ) const;<br />

void *operator new (size_t s);<br />

void operator <strong>de</strong>lete (void *ptr);<br />

~Nombre() {}; // Destructor que no hace nada<br />

};<br />

// Memoria para manejar un número fijo <strong>de</strong> instancias <strong>de</strong> la clase Nombre<br />

char memo[NOMBRES] [sizeof (Nombre)];<br />

int <strong>en</strong>_uso[NOMBRES];<br />

// Funciones miembro <strong>de</strong> la clase Nombre<br />

Nombre::Nombre ( const char *cad )<br />

{<br />

strncpy (nombre, cad, 25 );<br />

}<br />

void Nombre::Pres<strong>en</strong>ta ( void ) const<br />

{<br />

cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 13 -<br />

void Nombre::operator <strong>de</strong>lete ( void * ptr )<br />

{<br />

<strong>en</strong>_uso[ ( (char *)ptr - memo[0] ) / sizeof ( Nombre ) ] = 0;<br />

}<br />

void main ( void )<br />

{<br />

Nombre * directorio [NOMBRES];<br />

char nombre[25];<br />

}<br />

for (register int i=0; i


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 14 -<br />

<strong>Clases</strong> con punteros miembros<br />

Una práctica habitual es utilizar los operadores new y <strong>de</strong>lete <strong>en</strong> las funciones miembros <strong>de</strong> una clase, como ya<br />

se ha visto <strong>en</strong> varios <strong>de</strong> los ejemplos que hasta ahora se han pres<strong>en</strong>tado.<br />

Pero <strong>en</strong> este apartado se va a estudiar con más profundidad esta posibilidad, com<strong>en</strong>tando ciertos problemas que<br />

pue<strong>de</strong>n surgir, y la forma <strong>de</strong> solv<strong>en</strong>tarlos.<br />

Para ello se va a crear una clase Ca<strong>de</strong>na para el <strong>manejo</strong> <strong>de</strong> ca<strong>de</strong>nas <strong>de</strong> caracteres, que el lector pue<strong>de</strong><br />

personalizar y ampliar como ejercicio práctico <strong>de</strong> programación.<br />

CADENA.H<br />

#ifn<strong>de</strong>f __CADENA_H__<br />

#<strong>de</strong>fine __CADENA_H__<br />

#inclu<strong>de</strong> <br />

#inclu<strong>de</strong> <br />

class Ca<strong>de</strong>na {<br />

char *ca<strong>de</strong>na;<br />

unsigned longitud;<br />

public:<br />

Ca<strong>de</strong>na(void);<br />

Ca<strong>de</strong>na(const char *);<br />

Ca<strong>de</strong>na(char, unsigned);<br />

void CambiaCaracter(int, char);<br />

char MuestraCaracter(int) const;<br />

int Longitud(void) const {return longitud;}<br />

void Muestra(void) const {cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 15 -<br />

Ca<strong>de</strong>na::Ca<strong>de</strong>na(const char *cad)<br />

{<br />

longitud=strl<strong>en</strong>(cad);<br />

ca<strong>de</strong>na=new char[longitud+1];<br />

strcpy(ca<strong>de</strong>na, cad);<br />

}<br />

Ca<strong>de</strong>na::Ca<strong>de</strong>na(char c, unsigned n)<br />

{<br />

longitud=n;<br />

ca<strong>de</strong>na=new char[n+1];<br />

memset(ca<strong>de</strong>na, c, n);<br />

ca<strong>de</strong>na[n]='\0';<br />

}<br />

// Funciones miembro <strong>de</strong> la clase Ca<strong>de</strong>na<br />

void Ca<strong>de</strong>na::CambiaCaracter(int indice, char c)<br />

{<br />

if ( (indice >= 0) && (indice= 0) && (indice


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 16 -<br />

CADDEMO1.CPP<br />

// Programa: <strong>Clases</strong> con punteros miembros. Clase Ca<strong>de</strong>na<br />

// Fichero Principal.<br />

// Fichero: CADDEMO1.CPP<br />

#inclu<strong>de</strong> "ca<strong>de</strong>na.h"<br />

void main(void)<br />

{<br />

Ca<strong>de</strong>na Mica<strong>de</strong>na ("esta es mi ca<strong>de</strong>na.");<br />

}<br />

Mica<strong>de</strong>na.Muestra();<br />

cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 17 -<br />

#inclu<strong>de</strong> "ca<strong>de</strong>na.h"<br />

#inclu<strong>de</strong> <br />

void main(void)<br />

{<br />

Ca<strong>de</strong>na c1("Esta es la primera ca<strong>de</strong>na"),<br />

c2("Esta es otra ca<strong>de</strong>na");<br />

clrscr();<br />

cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 18 -<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 19 -<br />

En el ejemplo se han creado dos objetos c1 y c2 <strong>de</strong> la clase Ca<strong>de</strong>na. Para <strong>de</strong>spués hacer una asignación <strong>de</strong> c1 <strong>en</strong><br />

c2. Cuando se realiza una asignación <strong>de</strong> un objeto a otro, el compilador realizaría lo sigui<strong>en</strong>te:<br />

c2.longitud = c1.longitud;<br />

c2.ca<strong>de</strong>na = c1.ca<strong>de</strong>na;<br />

En la asignación <strong>de</strong>l miembro longitud no hay ningún problema. sin embargo, como el miembro ca<strong>de</strong>na es un<br />

puntero, el resultado <strong>de</strong> la asignación es que c1.ca<strong>de</strong>na y c2.ca<strong>de</strong>na apuntan a la misma zona <strong>de</strong> <strong>memoria</strong>. Es<br />

<strong>de</strong>cir, ambos objetos compart<strong>en</strong> la ca<strong>de</strong>na <strong>de</strong> caracteres.<br />

De forma gráfica:<br />

Aquí esta la explicación <strong>de</strong> los efectos no <strong>de</strong>seados que <strong>en</strong>contrábamos <strong>en</strong> el programa CADDEMO2.CPP.<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 20 -<br />

Otros problemas más graves se pue<strong>de</strong>n llegar a dar cuando el objeto sale <strong>de</strong>l alcance don<strong>de</strong> fue <strong>de</strong>finido. Cuando<br />

el <strong>de</strong>structor <strong>de</strong> la clase se llama para el objeto c1, se elimina dicho objeto liberándose también la <strong>memoria</strong><br />

don<strong>de</strong> apuntaba el puntero ca<strong>de</strong>na. Cuando el <strong>de</strong>structor se llama <strong>de</strong> nuevo para el objeto c2, se eliminará el<br />

puntero miembro ca<strong>de</strong>na. Pero como los dos miembros ca<strong>de</strong>na <strong>de</strong> ambos objetos ti<strong>en</strong><strong>en</strong> el mismo valor,<br />

significa que un puntero ha sido eliminado dos veces, esto pue<strong>de</strong> t<strong>en</strong>er consecu<strong>en</strong>cias imprevistas.<br />

A<strong>de</strong>más, el cont<strong>en</strong>ido original <strong>de</strong>l miembro ca<strong>de</strong>na <strong>de</strong>l objeto c2 se ha perdido, y ese bloque <strong>de</strong> <strong>memoria</strong> no será<br />

liberado.<br />

Este problema ocurre con cualquier clase que conti<strong>en</strong>e punteros miembro y reserva <strong>memoria</strong> <strong>de</strong>l<br />

almac<strong>en</strong>ami<strong>en</strong>to libre.<br />

En conclusión el operador <strong>de</strong> asignación que nos ofrece por <strong>de</strong>fecto el compilador, no es a<strong>de</strong>cuado para usarlo<br />

<strong>en</strong>tre clases. La solución será reemplazar el que se nos ofrece por <strong>de</strong>fecto por uno escrito por nosotros mismos, y<br />

que realm<strong>en</strong>te realice la asignación <strong>de</strong> forma a<strong>de</strong>cuada.<br />

Como ya es sabido, una <strong>de</strong> las características <strong>de</strong>l <strong>C++</strong> es que se permite la sobrecarga <strong>de</strong> operadores 12 . Lo que se<br />

va a hacer es dotar al operador <strong>de</strong> asignación <strong>de</strong> un significado especial cuando se refiera a la clase Ca<strong>de</strong>na.<br />

Para re<strong>de</strong>finir el operador <strong>de</strong> asignación se <strong>de</strong>be crear una función miembro que se llame operator=. De esta<br />

forma cuando se vaya a realizar una asignación <strong>en</strong>tre objetos <strong>de</strong> la clase Ca<strong>de</strong>na, el compilador utilizará el<br />

operador <strong>de</strong> asignación escrito para dicha clase.<br />

El nuevo operador <strong>de</strong> asignación pue<strong>de</strong> ser algo como:<br />

// Operador <strong>de</strong> asignación<br />

void Ca<strong>de</strong>na::operator= (const Ca<strong>de</strong>na &fu<strong>en</strong>te)<br />

{<br />

longitud = fu<strong>en</strong>te.longitud;<br />

<strong>de</strong>lete [ ] ca<strong>de</strong>na;<br />

ca<strong>de</strong>na = new char[longitud + 1];<br />

strcpy ( ca<strong>de</strong>na, fu<strong>en</strong>te.ca<strong>de</strong>na);<br />

}<br />

12 Que se estudiará con <strong>de</strong>talle más a<strong>de</strong>lante<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 21 -<br />

El operador recibe como parámetro una refer<strong>en</strong>cia como constante 13 a un objeto Ca<strong>de</strong>na. El resultado ahora es<br />

correcto, y se ilustra <strong>en</strong> la sigui<strong>en</strong>te figura:<br />

Una vez modificado el fichero cabecera, y el fichero cuerpo <strong>de</strong> la clase, compilar y ejecutar el programa<br />

CADDEMO2.CPP.<br />

Pero que ocurriría si se realiza una auto asignación c1 = c1; Esto que parece absurdo, y que casi nadie va a<br />

utilizar ti<strong>en</strong>e otras formas más comunes:<br />

Ca<strong>de</strong>na *Ptr_cad = &c1;<br />

// Más a<strong>de</strong>lante ...<br />

c1 = *Ptr_cad;<br />

Lo que ocurrirá es que <strong>en</strong> la <strong>de</strong>finición <strong>de</strong>l nuevo operador <strong>de</strong> asignación, primero borra la ca<strong>de</strong>na <strong>de</strong> caracteres,<br />

y <strong>de</strong>spués reserva un nuevo espacio, don<strong>de</strong> copia el cont<strong>en</strong>ido <strong>de</strong>l espacio recién reservado <strong>en</strong> él mismo. Esto<br />

ti<strong>en</strong>e como resultado que se introduce basura, y la pérdida <strong>de</strong> la ca<strong>de</strong>na <strong>de</strong> caracteres <strong>de</strong>l objeto. Para que el<br />

operador trabaje bi<strong>en</strong> <strong>en</strong> todos los casos, se <strong>de</strong>be utilizar el puntero this 14 para prever el caso <strong>de</strong> la auto<br />

asignación.<br />

Cuando una clase ti<strong>en</strong>e punteros miembro, se <strong>de</strong>be t<strong>en</strong>er <strong>en</strong> cu<strong>en</strong>ta a la hora <strong>de</strong> trabajar con constructores copia.<br />

Como se vio cuando se trató el tema <strong>de</strong> los constructores copia, estos iniciaban un objeto con los valores <strong>de</strong> otro<br />

objeto. Aquí se ti<strong>en</strong>e involucrado el uso <strong>de</strong>l operador asignación. Pero como se ha visto <strong>en</strong> este mismo apartado,<br />

el operador <strong>de</strong> asignación por <strong>de</strong>fecto nos va a dar resultados erróneos si la clase ti<strong>en</strong>e punteros miembro. Así si<br />

se hiciera lo sigui<strong>en</strong>te:<br />

Ca<strong>de</strong>na c2(c1);<br />

Se estaría iniciando el objeto c2 con los valores <strong>de</strong>l objeto c1, pero al utilizar el operador <strong>de</strong> asignación por<br />

<strong>de</strong>fecto, lo que se estaría haci<strong>en</strong>do es que tanto c1 como c2 apuntarán a la misma ca<strong>de</strong>na <strong>de</strong> caracteres.<br />

13 Indicando que dicho operador no pue<strong>de</strong> modificar el objeto que recibe<br />

14 El puntero this se estudia <strong>en</strong> el sigui<strong>en</strong>te apartado. Por lo que respecta a la correcta manera <strong>de</strong> crear el<br />

operador <strong>de</strong> asignación para la clase Ca<strong>de</strong>na, realizar el ejercicio 3 <strong>de</strong> este mismo capítulo.<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 22 -<br />

La solución pasa por construir un constructor copia propio. Como ejemplo se va a escribir el constructor copia<br />

para la clase Ca<strong>de</strong>na.<br />

// Constructor Copia<br />

Ca<strong>de</strong>na::Ca<strong>de</strong>na ( const Ca<strong>de</strong>na &fu<strong>en</strong>te)<br />

{<br />

longitud = fu<strong>en</strong>te.longitud;<br />

ca<strong>de</strong>na = new char [longitud +1];<br />

strcpy (ca<strong>de</strong>na, fu<strong>en</strong>te.ca<strong>de</strong>na);<br />

}<br />

La realización <strong>de</strong>l constructor copia es muy similar a la realización <strong>de</strong>l operador asignación. Pero hay algunas<br />

difer<strong>en</strong>cias <strong>en</strong>tre ellos:<br />

Un operador <strong>de</strong> asignación actúa sobre un objeto que existe, mi<strong>en</strong>tras que un constructor copia crea uno<br />

nuevo. Como resultado, un operador <strong>de</strong> asignación pue<strong>de</strong> t<strong>en</strong>er que borrar la <strong>memoria</strong> que se reservó<br />

originalm<strong>en</strong>te para el objeto que recibe los datos.<br />

Un operador <strong>de</strong> asignación ti<strong>en</strong>e que comprobar las auto asignaciones. El constructor copia no ti<strong>en</strong>e que<br />

hacerlo, porque <strong>en</strong> su caso no exist<strong>en</strong> auto asignaciones.<br />

Para permitir asignaciones <strong>en</strong>ca<strong>de</strong>nadas, un operador <strong>de</strong> asignación <strong>de</strong>be retornar *this. Por ser un<br />

constructor, el constructor copia no pue<strong>de</strong> <strong>de</strong>volver valores.<br />

El Puntero this<br />

Cada vez que se invoca a una función miembro, se le pasa automáticam<strong>en</strong>te un puntero al objeto que la ha<br />

invocado<br />

Se pue<strong>de</strong> acce<strong>de</strong>r a ese puntero utilizando la palabra reservada this<br />

El puntero this es un parámetro implícito a toda función miembro 15<br />

Para acce<strong>de</strong>r a un elem<strong>en</strong>to concreto <strong>de</strong> un objeto cuando se utiliza el objeto <strong>en</strong> sí, se emplea el operador<br />

punto (.)<br />

Para acce<strong>de</strong>r a un elem<strong>en</strong>to concreto <strong>de</strong> un objeto cuando se utiliza un puntero al objeto, se emplea el<br />

operador flecha (->)<br />

Cuando se llama a una función miembro, el compilador asigna la dirección <strong>de</strong>l objeto al puntero this, y <strong>de</strong>spués<br />

se llama a la función. Cada vez que una función miembro acce<strong>de</strong> a un dato miembro, se está utilizando <strong>de</strong> forma<br />

implícita el puntero this.<br />

Por ejemplo considérese el sigui<strong>en</strong>te programa <strong>en</strong> <strong>C++</strong>:<br />

#inclu<strong>de</strong> <br />

class D {<br />

int i,j,k;<br />

public:<br />

D() {i=j=k=0;}<br />

15 Excepto para las funciones miembro static<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 23 -<br />

void Mostrar(void)<br />

{<br />

cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 24 -<br />

El puntero this es un puntero constante, por lo tanto una función miembro no pue<strong>de</strong> cambiar el valor <strong>de</strong>l<br />

puntero, y hacer que apunte a otro sitio. Sin embargo, <strong>en</strong> las primeras versiones <strong>de</strong> <strong>C++</strong>, el puntero this no era<br />

constante, y esto permitía al programador hacer asignaciones al puntero this para personalizar el <strong>manejo</strong> <strong>de</strong> la<br />

<strong>memoria</strong> <strong>dinámica</strong>. Esto ya no está permitido <strong>en</strong> las últimas versiones <strong>de</strong> <strong>C++</strong>.<br />

Funciones estáticas y el puntero this<br />

Cuando <strong>en</strong> el capítulo anterior se abordó el tema <strong>de</strong> las funciones miembro static, se <strong>de</strong>finieron como aquellas<br />

que sólo podían trabajar con miembros dato estáticos. Esto es <strong>de</strong>bido a que este tipo <strong>de</strong> funciones carec<strong>en</strong> <strong>de</strong><br />

puntero this.<br />

Pero el problema surge si una función necesita <strong>de</strong> forma imperiosa acce<strong>de</strong>r a un miembro dato <strong>de</strong> una clase. Para<br />

solucionarlo se le ha <strong>de</strong> pasar <strong>de</strong> forma explícita un puntero this. Véase el sigui<strong>en</strong>te ejemplo:<br />

// Programa: Funciones miembro estáticas y el puntero this<br />

// Fichero: STTHIS.CPP<br />

#inclu<strong>de</strong> <br />

class Prueba {<br />

int a, b;<br />

static int <strong>de</strong>cre;<br />

public:<br />

Prueba () {a=b=0;}<br />

Prueba (int a1, int b1=0) {a=a1; b=b1;}<br />

static int Ejemplo(Prueba *E) {return E->a+E->b-<strong>de</strong>cre;}<br />

};<br />

int Prueba::<strong>de</strong>cre=1;<br />

void main (void)<br />

{<br />

Prueba P1(1,2);<br />

cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 25 -<br />

void main ()<br />

{<br />

Ca<strong>de</strong>na c1("Ca<strong>de</strong>na <strong>de</strong> ejemplo");<br />

ProcesaCa<strong>de</strong>na ( c1);<br />

}<br />

La función ProcesaCa<strong>de</strong>na recibe un objeto que se pasa por valor. Esto significa que la función ti<strong>en</strong>e su propia<br />

copia privada <strong>de</strong>l objeto.<br />

El parámetro <strong>de</strong> la función se inicia con el objeto que se pasa como argum<strong>en</strong>to. El compilador <strong>de</strong> forma implícita<br />

llama al constructor copia para realizar esta iniciación.<br />

Si no se <strong>de</strong>fine un constructor copia para realizar la iniciación, el compilador realiza su constructor copia por<br />

<strong>de</strong>fecto, y el objeto cad y el objeto c1 t<strong>en</strong>drían la misma ca<strong>de</strong>na <strong>de</strong> caracteres, y por lo tanto cualquier<br />

modificación <strong>en</strong> la ca<strong>de</strong>na <strong>de</strong> caracteres <strong>de</strong> cad modificaría la ca<strong>de</strong>na <strong>de</strong> caracteres <strong>de</strong>l objeto c1.<br />

También habría problemas <strong>en</strong> cuanto que el objeto cad ti<strong>en</strong>e un alcance local, y por lo tanto al salir <strong>de</strong> la función<br />

se llamaría al <strong>de</strong>structor. Esto significa que el objeto c1 t<strong>en</strong>dría un puntero que apuntaría a una zona <strong>de</strong> <strong>memoria</strong><br />

que ya ha sido liberada, lo cual iba a ser poco recom<strong>en</strong>dable.<br />

Ahora supongamos un extracto <strong>de</strong> fu<strong>en</strong>te <strong>en</strong> <strong>C++</strong> don<strong>de</strong> se incluye una función que retorna un objeto Ca<strong>de</strong>na.<br />

// Función que retorna un objeto Ca<strong>de</strong>na<br />

Ca<strong>de</strong>na RetCa<strong>de</strong>na ( void )<br />

{<br />

Ca<strong>de</strong>na valor ( "Esto es una prueba.");<br />

return valor;<br />

}<br />

void main ()<br />

{<br />

Ca<strong>de</strong>na c1;<br />

c1 = RetCa<strong>de</strong>na();<br />

}<br />

La función RetCa<strong>de</strong>na retorna un objeto Ca<strong>de</strong>na. El compilador llama al constructor copia para iniciar un<br />

objeto temporal y oculto <strong>en</strong> el alcance <strong>de</strong> la llamada, usando el objeto que se especifica <strong>en</strong> la s<strong>en</strong>t<strong>en</strong>cia return.<br />

Este objeto temporal es usado como parte <strong>de</strong>recha <strong>de</strong> la asignación que hay <strong>en</strong> el programa principal.<br />

De nuevo, se necesita un constructor copia. En otro caso el objeto temporal compartiría la misma ca<strong>de</strong>na <strong>de</strong><br />

caracteres que el objeto valor, el cual será eliminado cuando la función RetCa<strong>de</strong>na finalice su ejecución, y <strong>en</strong><br />

consecu<strong>en</strong>cia la asignación que se hace a c1 no está garantizada.<br />

Como regla a seguir, se <strong>de</strong>be <strong>de</strong>finir siempre un constructor copia y un operador <strong>de</strong> asignación cuando se<br />

t<strong>en</strong>ga una clase que cont<strong>en</strong>ga punteros miembro y reserve espacio <strong>de</strong>l almac<strong>en</strong>ami<strong>en</strong>to libre.<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 26 -<br />

Paso y retorno <strong>de</strong> refer<strong>en</strong>cias a objetos<br />

En el apartado anterior se ha visto como cada vez que se pasaba un objeto a una función por valor, se llamaba al<br />

constructor copia.<br />

Una forma <strong>de</strong> simular el paso por valor <strong>de</strong> un objeto, pero sin t<strong>en</strong>er que llamar al constructor copia sería utilizar<br />

refer<strong>en</strong>cias a objetos constantes.<br />

Estudiemos el mismo ejemplo que se vio <strong>en</strong> el apartado anterior, pero ahora la función recibe una refer<strong>en</strong>cia al<br />

objeto constante.<br />

// Función que recibe un objeto Ca<strong>de</strong>na como argum<strong>en</strong>to<br />

void ProcesaCa<strong>de</strong>na ( const Ca<strong>de</strong>na &cad )<br />

{<br />

// Usa el objeto cad<br />

}<br />

void main ()<br />

{<br />

Ca<strong>de</strong>na c1("Ca<strong>de</strong>na <strong>de</strong> ejemplo");<br />

ProcesaCa<strong>de</strong>na ( c1);<br />

}<br />

Ahora el constructor copia no es llamado porque no se ha construido un nuevo objeto. En su lugar se inicia una<br />

refer<strong>en</strong>cia con el objeto que se le pasa. Como resultado se usa el mismo objeto que <strong>en</strong> la llamada a la función.<br />

El uso <strong>de</strong> la palabra reservada const es <strong>de</strong>bido a que se quiere asegurar la integridad <strong>de</strong>l objeto, esto es, que no<br />

se pueda modificar el objeto <strong>en</strong> la función.<br />

Igualm<strong>en</strong>te que una función retorne una refer<strong>en</strong>cia <strong>en</strong> lugar <strong>de</strong> un objeto es mucho más efici<strong>en</strong>te. El constructor<br />

copia no se llama cuando se produce el valor <strong>de</strong> retorno, porque no se crea un objeto temporal, sólo se crea una<br />

refer<strong>en</strong>cia temporal.<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 27 -<br />

Ejercicios propuestos<br />

1. Indicar cuál <strong>de</strong> las sigui<strong>en</strong>tes frases es incorrecta y explicar los motivos<br />

a) La función <strong>de</strong>lete sirve para liberar zonas <strong>de</strong> <strong>memoria</strong> que han sido reservadas mediante new.<br />

b) El uso <strong>de</strong> malloc está prohibido <strong>en</strong> <strong>C++</strong><br />

c) La función set_new_handler sirve para que una función <strong>de</strong>terminada se <strong>en</strong>cargue <strong>de</strong>l <strong>manejo</strong> <strong>de</strong> los<br />

errores por falta <strong>de</strong> espacio <strong>en</strong> el <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> mediante el operador new.<br />

d) Cuando se crea un objeto con el operador new no se llama al constructor <strong>de</strong> la clase a m<strong>en</strong>os que se<br />

indique <strong>de</strong> forma explícita mediante argum<strong>en</strong>tos.<br />

e) Aunque no se <strong>de</strong>be, si se usa free para liberar un puntero a una instancia <strong>de</strong>vuelto por new, se llama al<br />

<strong>de</strong>structor si existe.<br />

2. Reescribir la función cambia() <strong>de</strong>l programa MALVNEW.CPP, reemplazando la función realloc por una<br />

simulación <strong>de</strong> ésta mediante el operador new.<br />

3. T<strong>en</strong>emos la clase Ca<strong>de</strong>na.<br />

class Ca<strong>de</strong>na {<br />

char *ca<strong>de</strong>na;<br />

unsigned longitud;<br />

public:<br />

Ca<strong>de</strong>na(void);<br />

Ca<strong>de</strong>na(const char *);<br />

Ca<strong>de</strong>na(char, unsigned);<br />

void CambiaCaracter(int, char);<br />

char MuestraCaracter(int) const;<br />

int Longitud(void) const {return longitud;}<br />

void Muestra(void) const {cout


Programación Ori<strong>en</strong>tada a Objetos<br />

3 – <strong>Clases</strong> <strong>manejo</strong> <strong>de</strong> <strong>memoria</strong> <strong>dinámica</strong> <strong>en</strong> <strong>C++</strong> - 28 -<br />

4. Modificar <strong>de</strong> nuevo el operador <strong>de</strong> asignación para que ahora también permita: c1=c2=c3;<br />

5. Un constructor copia ti<strong>en</strong>e como argum<strong>en</strong>to una refer<strong>en</strong>cia a un objeto <strong>en</strong> lugar <strong>de</strong> un objeto. ¿Por qué?<br />

6. Crear una clase apropiada para repres<strong>en</strong>tar el tipo <strong>de</strong> dato número complejo. Un número complejo se<br />

repres<strong>en</strong>tará como (a, b), don<strong>de</strong> a repres<strong>en</strong>ta la parte real y b la imaginaria.<br />

Construir a<strong>de</strong>más las funciones apropiadas para:<br />

Asignar valor a un número complejo<br />

Imprimir un número complejo con el formato (a, b)<br />

Sumar dos números complejos<br />

Restar dos números complejos<br />

Multiplicar dos números complejos<br />

Construir, a<strong>de</strong>más, un pequeño programa principal que compruebe el comportami<strong>en</strong>to <strong>de</strong> esta clase.<br />

7. Crear una clase apropiada para repres<strong>en</strong>tar el tipo <strong>de</strong> dato rectángulo.<br />

Construir a<strong>de</strong>más las funciones apropiadas para:<br />

Calcular el área <strong>de</strong> un rectángulo<br />

Calcular el perímetro <strong>de</strong> un rectángulo<br />

Dados dos rectángulos, <strong>de</strong>terminar cual es el mayor, t<strong>en</strong>i<strong>en</strong>do <strong>en</strong> cu<strong>en</strong>ta que el mayor es aquel que ti<strong>en</strong>e<br />

mayor área.<br />

Dados dos rectángulos, <strong>de</strong>terminar si sin idénticos. Ser idénticos implica que ti<strong>en</strong>e el mismo área y el<br />

mismo perímetro.<br />

Intercambiar los valores <strong>en</strong>tre dos rectángulos<br />

Or<strong>de</strong>nar un vector <strong>de</strong> rectángulos, <strong>de</strong> mayor a m<strong>en</strong>or<br />

8. Incluir a la clase Ca<strong>de</strong>na <strong>de</strong>finida <strong>en</strong> este docum<strong>en</strong>to los sigui<strong>en</strong>tes métodos:<br />

Invertir una ca<strong>de</strong>na<br />

Pasar a mayúsculas<br />

Determinar si es un palíndromo<br />

Construir a<strong>de</strong>más una función que permita or<strong>de</strong>nar <strong>de</strong> mayor a m<strong>en</strong>or un vector <strong>de</strong> Ca<strong>de</strong>nas.<br />

Ing<strong>en</strong>iería Técnica <strong>en</strong> Informática <strong>de</strong> Sistemas (3er curso)<br />

Departam<strong>en</strong>to <strong>de</strong> Informática y Automática – <strong>Universidad</strong> <strong>de</strong> Salamanca (versión Febrero 2003)

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

Saved successfully!

Ooh no, something went wrong!