Constructor de copia
Una clase tan sencilla como la siguiente:
class Cadena {
private:
char * cad;
void copiar(char *c)
{
delete [] cad;
cad = new char[ strlen( c ) + 1 ];
strcpy( cad, c );
}
public:
Cadena(char *c)
{ cad = NULL; copiar( c ); }
~Cadena()
{ delete[] cad; }
const char * getCadena() const
{ return cad; }
};
Encierra un error potencial. Supongamos que un objeto de esta clase se
pasa por valor a una función:
void f(Cadena c)
{
cout << c.getCadena() << endl;
}
El problema es que al llamar a f con un objeto de la clase Cadena ...
int main()
{
Cadena c1( "Hola" );
f( c1 );
cout << c1.getCadena() << endl;
}
Se produce un error de protección de memoria
después de visualizar "Hola" (el mostrado por la
función f).
El
problema es el paso por valor en f. El paso por valor es paso por
copia, y para crear el objeto local de la clase Cadena en f, a partir
del parámetro real c1, se utilizará un
constructor llamado "constructor de copia". Este
constructor acepta como parámetro un objeto de la clase
Cadena (su misma clase) para crear el nuevo objeto. Este constructor
de copia
existe por defecto en todas las clases de C++, aunque no se defina
explícitamente. Este constructor por defecto asigna uno a
uno los
miembros del nuevo objeto igualándolos con los del objeto ya
existente.
De ahí que los dos objetos, compartan en realidad, la misma
memoria.
Hay que tener en cuenta que al final de la función f, el
objeto c es
destruído, eliminando la memoria reservada: memoria que fue
reservada
por c1, y a la que ya no podrá acceder pues ya no existe (ha
sido
liberada). En el mejor de los casos, se producirá un error
en este
punto del programa. En el peor, el programa mostrará basura
y se
producirá un error de protección de memoria al
finalizar la función
main, y tratar c1 de volver a liberar la misma memoria liberada
anteriormente por c.
Para evitar ésto, podemos crear nuestro propio constructor
de copia, añadiéndolo a la clase Cadena:
Cadena(const Cadena &c)
{ cad = NULL; copiar( (char *) c.getCadena() ); }
O evitar SIEMPRE todos los pasos por
valor, como en f:
void f(const Cadena &c)
{
cout << c.getCadena() << endl;
}
Ejercicio
Crea la clase Pila. La clase Pila es capaz de albergar,
dinámicamente,
el número de elementos de tipo entero que se le pase en el
constructor.
El código:
Pila p1( 500 );
Crearía una pila con un máximo de quinientos
elementos.
Esta pila debe ser capaz de ejecutar sin errores el siguiente
código:
Pila p2( p1 );
Duplicando, correctamente, la pila p1 en p2.
Solución
La
solución consiste en crear el número de elementos
necesario en el constructor de la clase Pila, y crear, por supuesto, un
constructor de copia.
#include <iostream>
class Pila {
int * tope; // puntero al tope de la pila
int * contenido; // elementos de la pila
int tam; // tama. de la pila
public:
Pila(int tamPila);
Pila(const Pila &p); // constructor de copias
~Pila()
{ delete contenido; }
bool estaVacia() const
{ return ( tope == contenido ); }
bool estaLlena() const
{ return ( ( tope - contenido ) >= tam ); }
unsigned int getMax() const
{ return tam; }
void pop();
int top() const
{ return *tope; }
void push(int e);
};
Pila::Pila(int tamPila)
// Constructor de la clase pila
// tam_pila: num. de elementos max. en la pila
// realmente se almacenan tam_pila-1 elementos
// (en caso de pila sin eltos. tope y contenido son iguales)
{
tam = tamPila;
if ( ( contenido = new int[ tam ] ) == NULL ) {
std::cout << "\nERROR: memoria insuficiente" << std::endl;
exit( -1 );
}
tope = contenido;
}
void Pila::push(int e)
// Almacena un elemento en la pila
// En la primera pos. no se almacenan datos
{
if ( estaLlena() )
std::cout << "\nERROR: Pila llena" << std::endl;
else {
tope++;
*tope = e;
}
}
void Pila::pop()
// Elimina el tope de la pila
{
if ( estaVacia() ) {
std::cout << "\nERROR: Pila vacia" << std::endl;
}
else {
tope--;
}
}
Pila::Pila(const Pila &p)
{
int * temp1;
int * temp2;
// reserva de espacio
if ( (contenido = new int[ p.getMax() ] ) == NULL ) {
std::cout << "\ERROR: memoria insuficiente" << std::endl;
exit( -1 );
}
// copiar los elementos
temp1 = p.contenido;
temp2 = contenido;
while ( temp1 <= p.tope ) {
*temp2 = *temp1;
temp1++, temp2++;
}
// dar valor a las variables
tam = p.tam;
tope = contenido + ( p.tope - p.contenido );
}
void mostrar(Pila p)
{
std::cout << "\nElementos de la pila:\n";
while( !p.estaVacia() ) {
std::cout << p.top() << std::endl;
p.pop();
}
std::cout << std::endl << std::endl;
}
int main()
{
Pila p( 5 );
p.push( 1 );
p.push( 2 );
p.push( 3 );
Pila q = p;
mostrar( q );
mostrar( p );
}