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
}

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
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 copia
SpreadsheetCell 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