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 ...
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)