parent nodes: CPlusPlusApuntesC
Constructor de copia y operador de asignación
Extraído del libro: Wrox - Professional C++ - 2005
Author: César TM
Date: Febrero 2009
Whenever a class dynamically allocates memory, write a destructor, copy constructor,
and assignment operator.
Constructor de copia
class SpreadsheetCell
{
public:
(···)
SpreadsheetCell( const SpreadsheetCell & src );
(···)
protected:
double mValue;
string mString;
(···)
}
SpreadsheetCell::SpreadsheetCell( const SpreadsheetCell & src ) :
mValue( src.mValue ),
mString( src.mString )
{
; // all done with inicialization list
}
El compilador genera un constructor de copia por defecto igual al anterior, en algunos casos no es suficiente
Cada vez que se pasa por valor un objeto a una función se llama al constructor de copia de ese objeto. Para evitarlo hay que pasar los objetos por referencias o referencias constantes
Cada vez que se retorna un objeto de una función se llama al ctor. de copia de ese objeto. No es recomendable devolver un objeto por referencia ya que el objeto desaparecerá al finalizar la función y la referencia apuntará a un objeto inexistente
Más avanzado, copia con gestión de memoria
Constructor de copia de una clase que tiene una matriz (un array de arrays)Spreadsheet::Spreadsheet( const Spreadsheet & src )
{
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = new SpreadsheetCell * [ mWidth ];
for( int i = 0; i < mWidth; ++i )
{
mCells[ i ] = new SpreadsheetCell[ mHeight ];
}
for( int j = 0; j < mWidth; ++j )
{
for( int k = 0; k < mHeight; ++k )
{
mCells[ j ][ k ] = src.mCells[ j ][ k ];
}
}
}
Como se ve hay que copiar todos los miembros, no sólo los punteros.
Hay que volver a usar new ya que es un nuevo objeto.
Sí una clase tiene un contador único para cada instancia de esa clase, al usar el constructor de copia para crear un objeto2 a partir de un objeto1, no debemos copiar el valor de ese identificador, sino darle el siguiente que toque (usando una función que calcule cuál es el siguiente).
Asignación
SpreadsheetCell myCell(5); // constructor por argumentos
SpreadsheetCell anotherCell; // constructor por defecto
anotherCell = myCell; // asignación
La asignación no es un constructor, anotherCell debe estár ya construido
La asignación no copia el objeto sino que lo asigna
La autoasignación es legal en C++ y no se debe prohibir, sino que hay que devolver el propio objeto
class SpreadsheetCell
{
public:
(···)
SpreadsheetCell & operator=( const SpreadsheetCell & rhs );
(···)
protected:
double mValue;
string mString;
(···)
}
SpreadsheetCell& SpreadsheetCell::operator=( const SpreadsheetCell & rhs )
{
if( this == & rhs ) // to avoid self-assignment
return( *this );
mValue = rhs.mValue;
mString = rhs.mString;
return( *this );
}
Más avanzado, gestión de memoria
Spreadsheet& Spreadsheet::operator=( const Spreadsheet & rhs )
{
// Check for self-assignment.
if( this == &rhs )
return( *this );
// Free the old memory. This block is identical to the destructor
for( int i = 0; i < mWidth; ++i)
{
delete[] mCells[ i ];
}
delete[] mCells;
// Copy the new memory. This block is identical to the copy constructor
mWidth = rhs.mWidth;
mHeight = rhs.mHeight;
mCells = new SpreadsheetCell * [ mWidth ];
for( int j = 0; j < mWidth; ++j )
{
mCells[ j ] = new SpreadsheetCell[ mHeight ];
}
for( int k = 0; k < mWidth; ++k )
{
for( int m = 0; m < mHeight; ++m )
{
mCells[ k ][ m ] = rhs.mCells[ k ][ m ];
}
return( *this );
}
Dado que hay una parte del operador de asignación que es igual al constructor de copia se puede sacar a una función común que será llamada por ambos. Aunque en la función común no podremos usar la lista de inicialización pues no es un constructor.void
Spreadsheet::copyFrom( const Spreadsheet & src )
{
int i;
int j;
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = new SpreadsheetCell * [ mWidth ];
for( i = 0; i < mWidth; ++i )
{
mCells[ i ] = new SpreadsheetCell[ mHeight ];
}
for( i = 0; i < mWidht; ++i )
{
for( j = 0; j < mHeight; ++j )
{
mCells[ i ][ j ] = src.mCells[ i ][ j ];
}
}
}
// copy constructor
Spreadsheet::Spreadsheet( const Spreadsheet & src )
{
copyFrom( src );
}
// Assignment operator
Spreadsheet& Spreadsheet::operator=( const Spreadsheet & rhs )
{
// Check for self-assignment.
if( this == &rhs )
return( *this );
// Free the old memory.
int i;
for( i = 0; i < mWidth; ++i )
{
delete[] mCells[ i ];
}
delete[] mCells;
// Copy the new memory.
copyFrom( rhs );
return( *this );
Asignación y copia
A veces el signo = no indica asignación, sí se usa en la misma línea que la declaración del objeto, se trata de una llamada al constructor de copiaSpreadsheetCell aThirdCell = myCell; // estas dos líneas
SpreadsheetCell aThirdCell( myCell ); // son equivalentes
string SpreadsheetCell::getString()
{
return( mString );
}
SpreadsheetCell myCell2(5); // Constructor de SpreadSheetCell por argumentos
string s1; // Constructor de string por defecto
s1 = myCell2.getString(); // Constructor de string de una cadena temporal
// Asignación de string de la cadena temporal a la cadena s1
// Destructor de la cadena temporal
SpreadsheetCell myCell3(5); // Constructor de SpreadSheetCell por argumentos
string s2 = myCell3.getString(); // Constructor de string de una cadena temporal
// Constructor de copia de s2 a la cadena temporal
// Destructor de la cadena temporal
Si un objeto contiene otros objetos, el constructor de copia generado por defecto por el compilador llamará a los constructores de copia de los objetos internos recursivamente cada vez que se copie el objeto padre. Escribiendo nuestro propio constructor de copia podemos lograr lo mismo usando una lista de inicialización ya que el constructor de copia es un constructor se pueden inicializar sus miembros en lugar de hacer asignaciones de valores. De este modo se evita llamar al operador de asignación de cada uno de los objetos.SpreadsheetCell::SpreadsheetCell( const SpreadsheetCell & src ) :
mValue(src.mValue),
mString(src.mString)
{
; // all done with inicialization list
}
// instead of
SpreadsheetCell::SpreadsheetCell( const SpreadsheetCell & src ) :
mString(src.mString)
{
mValue = src.mValue; // assignament operator, if mValue has it
mString = src.mString; // assignament operator of class string
}
Si queremos evitar el paso por valor y la asignación hay que declarar como privados el constructor de copia y el operador de asinación. No hará falta definirlos en el archivo cpp ya que el linker nunca llegará a ellos, si el copilador encuentra una asignación o un paso por valor se quejará.
_________________________________________________________________________________________________________
CPlusPlusApuntesC