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 );
}