what the pointer operator objects assignment c++ pointers memory-management copy-constructor

the - operador de asignación vs constructor de copia C++



pointers to objects in c++ (3)

El constructor de copia no se utiliza durante la asignación. El constructor de copia en su caso se utiliza al pasar argumentos a la función displayInteger . El segundo parámetro se pasa por valor, lo que significa que no está habilitado por el contructor de copia.

Su versión del constructor de copias realiza una copia profunda de los datos que pertenecen a la clase (al igual que lo hace su operador de asignación). Por lo tanto, todo funciona correctamente con su versión de copia constructor.

Si elimina su propio constructor de copia, el compilador generará uno para usted implícitamente. El constructor de copia generado por el compilador realizará una copia superficial del objeto. Esto violará la "Regla de tres" y destruirá la funcionalidad de su clase, que es exactamente lo que observa en su experimento. Básicamente, la primera llamada a displayInteger daña su objeto intVal1 y la segunda llamada a displayInteger daña su objeto intVal2 . Después de que ambos objetos estén rotos, es por eso que la tercera llamada a displayInteger muestra basura.

Si cambia la declaración de displayInteger a

void displayInteger( char* str, const Integer &intObj )

su código "funcionará" incluso sin un constructor de copia explícito. Pero no es una buena idea ignorar la "Regla de los Tres" en cualquier caso. Una clase implementada de esta manera tiene que obedecer la "Regla de los Tres" o debe ser no copiable.

Tengo el siguiente código para probar mi comprensión de los punteros básicos en C ++:

// Integer.cpp #include "Integer.h" Integer::Integer() { value = new int; *value = 0; } Integer::Integer( int intVal ) { value = new int; *value = intVal; } Integer::~Integer() { delete value; } Integer::Integer(const Integer &rhInt) { value = new int; *value = *rhInt.value; } int Integer::getInteger() const { return *value; } void Integer::setInteger( int newInteger ) { *value = newInteger; } Integer& Integer::operator=( const Integer& rhInt ) { *value = *rhInt.value; return *this; } // IntegerTest.cpp #include <iostream> #include <cstdlib> #include "Integer.h" using namespace std; void displayInteger( char* str, Integer intObj ) { cout << str << " is " << intObj.getInteger() << endl; } int main( int argc, char* argv[] ) { Integer intVal1; Integer intVal2(10); displayInteger( "intVal1", intVal1 ); displayInteger( "intVal2", intVal2 ); intVal1 = intVal2; displayInteger( "intVal1", intVal1 ); return EXIT_SUCCESS; }

Este código funciona exactamente como se espera, se imprime:

intVal1 is 0 intVal2 is 10 intVal1 is 10

Sin embargo, si elimino el constructor de copia, se imprime algo como:

intVal1 is 0 intVal2 is 10 intVal1 is 6705152

No entiendo por qué este es el caso. Entiendo que el constructor de copia se utiliza cuando la asignación es a un objeto que no existe. Aquí existe intVal1 , ¿por qué no se llama al operador de asignación?


El problema que estás experimentando está causado por el constructor de copia predeterminado, que copia el puntero pero no lo asocia con la memoria recién asignada (como lo hace la implementación del constructor de copia). Cuando se pasa objeto por valor, se crea una copia y cuando la ejecución se sale del ámbito, esta copia se destruye. delete del destructor invalida el puntero de value del objeto intVal1 , lo que lo hace colgar , y la anulación de la referencia provoca un comportamiento indefinido .

Los resultados de la depuración podrían usarse para comprender el comportamiento de su código:

class Integer { public: Integer() { cout << "ctor" << endl; value = new int; *value = 0; } ~Integer() { cout << "destructor" << endl; delete value; } Integer(int intVal) { cout << "ctor(int)" << endl; value = new int; *value = intVal; } Integer(const Integer &rhInt) { cout << "copy ctor" << endl; value = new int; *value = *rhInt.value; } Integer& operator=(const Integer& rhInt){ cout << "assignment" << endl; *value = *rhInt.value; return *this; } int *value; }; void foo(Integer intObj) { cout << intObj.value << " " << *(intObj.value) << endl; }

Ahora salida de este código:

Integer intVal1; Integer intVal2(10); foo( intVal1 ); foo( intVal2 ); intVal1 = intVal2; foo( intVal1 );

es:

ctor
ctor (int)
copia ctor
0x9ed4028 0
incinerador de basuras
copia ctor
0x9ed4038 10
incinerador de basuras
asignación
copia ctor
0x9ed4048 10
incinerador de basuras
incinerador de basuras
incinerador de basuras

que muestra que el constructor de copia se utiliza cuando se pasan objetos por valor. Sin embargo, es importante notar que aquí está el destructor llamado a la devolución de su función. Y si elimina su implementación del constructor de copia, la salida es:

ctor
ctor (int)
0x8134008 0
incinerador de basuras
0x8134018 10
incinerador de basuras
asignación
0x8134008 135479296
incinerador de basuras
incinerador de basuras
incinerador de basuras

demostrando que la primera copia llamada delete en el mismo puntero (apuntando a 0x8134008 ) que fue utilizada por la tercera copia más adelante, donde se usó la memoria apuntada por este puntero colgante.


Piensa en esta llamada:

displayInteger( "intVal1", intVal1 );

Está creando una copia de intVal1 en el parámetro displayInteger de displayInteger :

void displayInteger( char* str, Integer intObj ) { cout << str << " is " << intObj.getInteger() << endl; }

Esa copia apuntará al mismo int que intVal1 . Cuando displayInteger regresa, intObj se destruye, lo que hará que se destruya int , y el puntero en intVal1 apunte a un objeto no válido. En ese momento, todas las apuestas están desactivadas (también conocido como comportamiento indefinido) si intenta acceder al valor. Algo similar ocurre con intVal2 .

A un nivel más general, al eliminar el constructor de copia, está violando la Regla de tres, que generalmente conduce a este tipo de problemas.