En el siguiente programa, tengo la intención de copiar char* line
contenidos de un objeto a otro strcpy
. Sin embargo, cuando finaliza el programa, el destructor de obj2
trabajos funciona bien pero el dtor de los obj
bloqueos. gdb muestra diferentes direcciones de line
ambos objetos.
class MyClass {
public:
char *line;
MyClass() {
line = 0;
}
MyClass(const char *s) {
line = new char[strlen(s)+1];
strcpy(line, s);
}
~MyClass() {
delete[] line;
line = 0;
}
MyClass &operator=(const MyClass &other) {
delete[] line;
line = new char[other.len()+1];
strcpy(line, other.line);
return *this;
}
int len(void) const {return strlen(line);}
};
int main() {
MyClass obj("obj");
MyClass obj2 = obj;
MyClass obj1; MyClass obj2 = obj1;
seguirá segfault porque llamará astrlen(obj1.line)
cuál esstrlen(NULL)
. Como lo haríaMyClass obj1; obj1.len();
.MyClass obj1; obj1.len();
es un comportamiento indefinido invocarstrlen
un puntero nulo.Respuestas:
Con
no tienes asignación, tienes copia de construcción . Y no sigue las reglas de tres, cinco o cero ya que no tiene un constructor de copia, por lo que el generado por defecto simplemente copiará el puntero.
Eso significa que después de esto tiene dos objetos cuyo
line
puntero apunta a la misma memoria. Eso conducirá a un comportamiento indefinido una vez que uno de los objetos se destruya, ya que deja al otro con un puntero no válido.La solución ingenua es agregar un constructor de copia que haga una copia profunda de la cadena en sí, de manera similar a lo que está haciendo su operador de asignación.
Una mejor solución sería utilizar
std::string
en su lugar sus cadenas y seguir la regla de cero.fuente
Necesita crear un constructor de copia. Esto tiene que hacer la regla de 3/5 . Está creando
obj2
, lo que significa que se invoca un constructor de copia, no el operador de asignación de copia.Debido a que no tiene un constructor de copia, se realiza una copia "superficial". Esto significa que
line
se copia por valor. Ya que es un puntero, tantoobj
yobj2
están apuntando a la misma memoria. Se llama al primer destructor y borra esa memoria bien. Se llama al segundo constructor y se produce una doble eliminación, lo que causa su falla de segmentación.Cuando se trata de C-Strings, absolutamente no puede perder el carácter nulo. El problema es que es extremadamente fácil perder. También le faltaba una protección de autoasignación en su operador de asignación de copias. Eso podría haberte llevado a bombardear accidentalmente un objeto. Agregué un
size_
miembro y lo usé enstrncpy()
lugar destrcpy()
porque poder especificar un número máximo de caracteres es increíblemente importante en el caso de perder un carácter nulo. No evitará el daño, pero lo mitigará.Hay otras cosas que me gustaron usando la Inicialización de miembro predeterminada (a partir de C ++ 11) y el uso de una lista de inicialización de miembro constructor . Mucho de esto se vuelve innecesario si puede usarlo
std::string
. C ++ puede ser "C con clases", pero vale la pena tomarse el tiempo para explorar realmente lo que el lenguaje tiene para ofrecer.Algo que un constructor y destructor de copias de trabajo nos permite hacer es simplificar nuestro operador de asignación de copias utilizando el "modismo de copia e intercambio".
Enlace a la explicación .
fuente