c++ - ¿Por qué no se llama al destructor en el operador delete?
delete-operator (3)
Intenté llamar a
::delete
para una clase en el
operator delete
of it.
Pero el destructor no se llama.
MyClass
una clase
MyClass
cuyo
operator delete
se ha sobrecargado.
La
operator delete
global del
operator delete
también está sobrecargada.
La
operator delete
de
operator delete
sobrecargado de
MyClass
llamará a la
operator delete
global sobrecargado.
class MyClass
{
public:
MyClass() { printf("Constructing MyClass.../n"); }
virtual ~MyClass() { printf("Destroying MyClass.../n"); }
void* operator new(size_t size)
{
printf("Newing MyClass.../n");
void* p = ::new MyClass();
printf("End of newing MyClass.../n");
return p;
}
void operator delete(void* p)
{
printf("Deleting MyClass.../n");
::delete p; // Why is the destructor not called here?
printf("End of deleting MyClass.../n");
}
};
void* operator new(size_t size)
{
printf("Global newing.../n");
return malloc(size);
}
void operator delete(void* p)
{
printf("Global deleting.../n");
free(p);
}
int main(int argc, char** argv)
{
MyClass* myClass = new MyClass();
delete myClass;
return EXIT_SUCCESS;
}
El resultado es:
Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...
Real:
Solo hay una llamada al destructor antes de llamar al
operator delete
sobrecargado para
operator delete
MyClass
.
Esperado:
Hay dos llamadas al destructor.
Uno antes de llamar al
operator delete
sobrecargado
operator delete
MyClass
.
Otro antes de llamar al
operator delete
global
operator delete
.
Está utilizando mal el
operator new
y el
operator delete
.
Estos operadores son funciones de asignación y desasignación.
No son responsables de construir o destruir objetos.
Solo son responsables de proporcionar la memoria en la que se colocará el objeto.
Las versiones globales de estas funciones son
::operator new
y
::operator delete
.
::new
y
::delete
son expresiones nuevas / delete, al igual que
new
/
delete
, que difieren de esas, en que
::new
y
::delete
omitirán las sobrecargas del
operator new
específico de la clase
operator new
/
operator delete
.
Las nuevas expresiones / delete construyen / destruyen
y
asignan / desasignan (llamando al
operator new
apropiado
operator new
u
operator delete
antes de la construcción o después de la destrucción).
Dado que su sobrecarga solo es responsable de la parte de asignación / desasignación, debe llamar a
::operator new
y
::operator delete
lugar de
::new
y
::delete
.
La
delete
en
delete myClass;
es responsable de llamar al destructor.
::delete p;
no llama al destructor porque
p
tiene el tipo
void*
y, por lo tanto, la expresión no puede saber a qué destructor llamar.
Probablemente llamará a su
::operator delete
reemplazado
::operator delete
para desasignar la memoria, aunque el uso de un
void*
como operando para una
expresión de eliminación
está mal formado (vea la edición a continuación).
::new MyClass();
llama a su reemplazado
::operator new
para asignar memoria y construye un objeto en él.
El puntero a este objeto se devuelve como
void*
a la nueva expresión en
MyClass* myClass = new MyClass();
, que luego construirá
otro objeto
en esta memoria, finalizando la vida útil del objeto anterior sin llamar a su destructor.
Editar:
Gracias al comentario de @ MM sobre la pregunta, me di cuenta de que un
void*
como operando para
::delete
realidad está mal formado.
(
[expr.delete]/1
) Sin embargo, los principales compiladores parecen haber decidido solo advertir sobre esto, no por error.
Antes de que estuviera mal formado, el uso de
::delete
en un
void*
ya tenía un comportamiento indefinido, vea
esta pregunta
.
Por lo tanto, su programa está mal formado y no tiene ninguna garantía de que el código realmente haga lo que describí anteriormente si aún logra compilarse.
Como señaló @SanderDeDycker debajo de su respuesta, también tiene un comportamiento indefinido porque al construir otro objeto en la memoria que ya contiene un objeto
MyClass
sin llamar primero al destructor de ese objeto, está violando
[basic.life]/5
que prohíbe hacerlo si El programa depende de los efectos secundarios del destructor.
En este caso, la instrucción
printf
en el destructor tiene ese efecto secundario.
Sus sobrecargas específicas de clase se realizan incorrectamente. Esto se puede ver en su salida: ¡se llama al constructor dos veces!
En el
operator new
específico de clase
operator new
, llame al operador global directamente:
return ::operator new(size);
Del mismo modo, en la
operator delete
específico de la clase, haga:
::operator delete(p);
Consulte la
operator new
página de referencia del
operator new
para obtener más detalles.
Ver referencia de CPP :
operator delete
,operator delete[]
Desasigna el almacenamiento previamente asignado por un
operator new
coincidenteoperator new
. Estas funciones de desasignación se llaman mediante expresiones de eliminación y nuevas expresiones para desasignar la memoria después de destruir (o no construir) objetos con una duración de almacenamiento dinámico. También pueden llamarse utilizando la sintaxis de llamada a función regular.
Delete (y new) solo son responsables de la parte de ''gestión de memoria''.
Por lo tanto, está claro y se espera que el destructor solo se llame una vez, para limpiar la instancia del objeto. Se llamaría dos veces, cada destructor tendría que verificar si ya se había llamado.