¿ya leíste la sección
10.4.4.1 Copying Objects del libro
The C++ Programming Language, 3rd. Ed.? En el menciona cómo tratar los casos cuando tienes una clase cuyos miembros son apuntadores. (Lo que sigue es una traducción libre de un extracto de esa sección.) Básicamente tienes que definir dos funciones: una que trate el caso de cuando creas un objeto de la clase y al mismo tiempo la inicializas con otro objeto de la misma clase, y otra de cuando tienes ya dos objetos de la clase, y le asignas uno de ellos al otro. O sea, si
Table es una clase, en la siguiente función tendrías problemas:
Código:
void h ()
{
Table t1;
Table t2 = t1 ; // inicialización por copia: problema
Table t3;
t3 = t2; // copia por asignación: problema
}
Si no declaras funciones para manejar esos casos, el compilador manejará por defecto estas copias copiando miembro a miembro las clases.
Si solicitas memoria dinámica al construir un objeto de la clase y después la liberas con el destructor, entonces se llamará al constructor dos veces, una para
t1 y otra para
t3. Para
t2 no se llama, ya que fue inicializado por copia. Sin embargo, el destructor es llamado tres veces: para
t1,
t2 y
t3. Dado que la asignación por defecto se hace miembro a miembro, los tres objetos tendrán un apuntador hacia la memoria solicitada al comienzo por
t1. La memoria solicitada por
t3 estará sin referenciar, ya que su apuntador se modificó con la asignación
t2=t3. Así, en la ausencia de un recolector automático de basura, esta memoria se perderá para siempre. Por otro lado, la memoria solicitada por
t1 aparece referenciada tres veces, de manera se borrará otras tantas veces. El resultado es indefinido y probablemente desastroso.
Tales anomalías se corrigen definiendo dos funciones, cada una de las cuales trata los dos casos anteriores:
Código:
class Table {
// ...
Table (const Table&); // constructor de copia
Table& operator= (const Table&); // copia por asignación
};
El programador puede definir cualquier significado apropiado para estas operaciones, pero el tradicional para esta clase de contenedores es copiar los elementos del contenedor (o al menos dar al usuario del contenedor la apariencia de que una copia se ha hecho). Por Ejemplo:
Código:
Table::Table (const Table&t) // constructor de copia
{
p = new Name[sz=t.sz];
for (int i=0; i<sz; i++) p[i] = t.p[i];
}
Table& Table::operator=(const Table&t) // asignación
{
if (this!= &t) { // cuidado de la auto-asignación
t = t
delete []p;
p = new Name[sz=t.sz];
for (int i=0 ; i<sz ; i++) p[i] = t.p[i];
}
return *this;
}
Como casi siempre es el caso, el constructor por copia y la copia por asignación difieren considerablemente. La razón fundamental es que el constructor por copia inicializa memoria no inicializada, mientras que el operador de la copia por asignación debe de tratar con un objeto ya construído.
La asignación puede optimizarse en algunos casos, pero la estrategia general para un operador de asignación es simple: protegerse contra auto-asignaciones, borrar elementos antiguos, inicializar y copiar en nuevos elementos. Usualmente cada miembro no estático debe copiarse.