c++ - eliminar un puntero NULL no llama a la eliminación sobrecargada cuando se escribe destructor
memory-management (7)
El destructor de objetos se llama antes del operador de eliminación. Así que supongo que intenta llamar al destructor, se da cuenta de que el puntero es NULL, por lo tanto
- no llama a destructor que necesita una instancia
- detiene la operación de eliminación allí (tipo de optimización de velocidad en mi humilde opinión).
Como dijo Neil, si w contiene un Widget, debería funcionar.
class Widget
{
public:
Widget() {
cout<<"~Widget()"<<endl;
}
~Widget() {
cout<<"~Widget()"<<endl;
}
void* operator new(size_t sz) throw(bad_alloc) {
cout<<"operator new"<<endl;
throw bad_alloc();
}
void operator delete(void *v) {
cout<<"operator delete"<<endl;
}
};
int main()
{
Widget* w = 0;
try {
w = new Widget();
}
catch(bad_alloc) {
cout<<"Out of Memory"<<endl;
}
delete w;
getch();
return 1;
}
En este código, delete w
no llama al operador de delete
sobrecargado cuando el destructor está allí. Si se omite el destructor, se llama a la delete
sobrecargada. ¿Por qué esto es tan?
Salida cuando ~ Widget () está escrito
operador nuevo
Sin memoria
Salida cuando ~ Widget () no está escrito
operador nuevo
Sin memoria
operador borrar
El motivo es que si tiene un destructor, la llamada al operador de eliminación se realiza desde el destructor de eliminación escalar, que en VC contiene la llamada tanto al destructor como al operador de eliminación. El compilador proporciona un código que verifica si está intentando eliminar un puntero NULL. La eliminación de dicho puntero es legal, por supuesto, pero el destructor de dicho objeto no debe invocarse, ya que puede contener el uso de variables miembro. Para eso, se evita la llamada al destructor de eliminación escalar y, como resultado, también se evita la llamada al operador de eliminación.
Cuando no hay destructor, el compilador simplemente llama al operador de eliminación directamente, sin generar el destructor de eliminación escalar. Por lo tanto, en tales casos, el operador de eliminación se invoca después de todo.
En primer lugar, esto puede simplificarse para delete (Widget*)0
- todo lo demás en tu main()
es innecesario para reproducir esto.
Es un artefacto de generación de código que ocurre porque 1) la operator delete
definida por el usuario debe ser capaz de manejar valores NULL, y 2) el compilador intenta generar el código más óptimo posible.
Primero consideremos el caso cuando no está involucrado un destructor definido por el usuario. Si ese es el caso, no hay código para ejecutar en la instancia, excepto para la operator delete
. No tiene sentido comprobar nulo antes de transferir el control a la operator delete
, porque este último debería hacer una comprobación de todos modos; y entonces el compilador simplemente genera la llamada incondicional de la operator delete
del operator delete
(y usted ve que este último imprime un mensaje).
Ahora el segundo caso - destructor fue definido. Esto significa que su declaración de delete
realmente se expande en dos llamadas: destructor y operator delete
. Pero destructor no puede invocarse con seguridad en un puntero nulo, porque podría intentar acceder a los campos de clase (el compilador podría darse cuenta de que su destructor en particular no lo hace realmente y por lo tanto es seguro llamar con nulo this
, pero parece que no lo hacen no te molestes en la práctica). Por lo tanto, inserta una verificación nula allí antes de la llamada al destructor. Y una vez que el cheque ya está allí, también podría usarlo omitir la llamada al operator delete
también; después de todo, se requiere que sea una operación no operativa de todos modos, y ahorrará una verificación adicional sin sentido para nulo dentro del operator delete
en caso el puntero en realidad es nulo.
Por lo que puedo ver, nada en esto está garantizado de ninguna manera por las especificaciones ISO C ++. Es solo que ambos compiladores hacen la misma optimización aquí.
Me gustaría dejar un comentario, en lugar de responder, no tenía suficientes privilegios como nuevo miembro.
Una excepción se plantea durante la creación del objeto. El destructor no se llama, ya que el objeto en sí no se crea.
Eso también se puede observar, ya que los mensajes del constructor y el destructor no se muestran.
Pero, se llama a la eliminación cuando el destructor no está definido. Si se piensa en el directon que cuando Destrcutor no está definido, C ++ Compiler lo considera como cualquier otro operador, el compilador por defecto proporciona un destructor cuando no está definido.
No tengo una buena respuesta, pero simplifiqué un poco el problema. El siguiente código elimina el operador nuevo y el manejo de excepciones:
#include <iostream>
using namespace std;
class Widget {
public:
Widget() {
cout<<"Widget()"<<endl;
}
~Widget() {
cout<<"~Widget()"<<endl;
}
void operator delete(void *v) {
cout << "operator delete" << endl;
}
};
int main() {
Widget* w = 0;
cout << "calling delete" << endl;
delete w;
}
Esto todavía muestra el mismo comportamiento y deséo tanto en VC ++ como en g ++.
Por supuesto, eliminar un puntero NULL no es operativo, por lo que el compilador no tiene que llamar al operador delete. Si uno realmente asigna un objeto:
Widget* w = new Widget;
entonces las cosas funcionan como se esperaba
Recuerdo algo similar en el operador delete hace un tiempo en comp.lang.c ++. Moderado. No puedo encontrarlo ahora, pero la respuesta dice algo como esto ...
Desafortunadamente, la especificación del lenguaje no es lo suficientemente clara sobre si el control debe entrar en la "eliminación de operador" sobrecargada cuando se invoca la expresión de eliminación en el puntero nulo del tipo correspondiente, aunque el estándar sí dice que delete-expression en null -pointer es un no-op.
Y James Kanze dijo específicamente:
Sigue siendo la responsabilidad del operador delete (o delete []) verificar; el estándar no garantiza que no recibirá un puntero nulo; el estándar requiere que no sea operativo si se le da un puntero nulo. O que la implementación puede llamarlo. Según el último borrador, "el valor del primer argumento suministrado a una función de desasignación puede ser un valor de puntero nulo; de ser así, y si la función de desasignación es una suministrada en la biblioteca estándar, la llamada no tiene efecto". No estoy muy seguro de qué significan las implicaciones de que "se proporciona en la biblioteca estándar", literalmente, ya que su función no es proporcionada por la biblioteca estándar, la frase no parece aplicarse . Pero de alguna manera, eso no tiene sentido
Recuerdo esto porque tuve un problema similar en algún momento y había conservado la respuesta en un archivo .txt.
ACTUALIZACIÓN-1:
Oh, lo encontré aquí . Lea también este informe de defectos del enlace. Entonces, la respuesta es No especificada . Capítulo 5.3.5 / 7 .
Intentaba eliminar un puntero NULL. Entonces, el destructor no fue llamado.
class Widget
{
public:
Widget()
{
cout<<"Widget()"<<endl;
}
~Widget()
{
cout<<"~Widget()"<<endl;
}
void* operator new(size_t sz) throw(bad_alloc)
{
cout<<"operator new"<<endl;
return malloc(sizeof(Widget));
//throw bad_alloc();
}
void operator delete(void *v)
{
cout<<"operator delete"<<endl;
}
};
int main()
{
Widget* w = NULL;
try
{
w = new Widget();
//throw bad_alloc();
}
catch(bad_alloc)
{
cout<<"Out of Memory"<<endl;
}
delete w;
}
Salida:
operador nuevo
Widget ()
~ Widget ()
operador borrar