2009-07-10 8 views
16
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 a la delete operador sobrecargado cuando el destructor está allí. Si se omite el destructor, se llama al delete sobrecargado. ¿Por qué esto es tan?eliminar un puntero NULL no llama sobrecargado eliminar al destructor está escrito

Salida cuando ~ Widget() se escribe

operador nueva
fuera de la memoria

Salida cuando ~ Widget() no está escrito

operador nuevo
Sin memoria
operador eliminar

+0

¡Esta es una pregunta extrañamente fascinante! Acabo de buscar en Effective C++ de Scott Meyer (desafortunadamente solo tengo 2nd Ed) y no pude ver nada sobre esto. –

Respuesta

21

recuerdo algo similar en operador de eliminar hace un tiempo en un borrador .lang.C++. moderado. No puedo encontrar ahora, pero la respuesta indiqué algo como esto ..

Desafortunadamente, la especificación del lenguaje no es suficientemente clara sobre si el control debe ir en el 'operador delete' sobrecargado cuando el borrado -expression se invoca en el puntero nulo del tipo correspondiente , aunque el estándar dice que delete-expression en null-pointer es un no-op.

Y James Kanze dijo específicamente:

que sigue siendo el responisiblity de operador delete (borrar o []) para cheque; el estándar no garantiza que no se le dará un puntero nulo; el estándar requiere que sea no operativa si se le da un puntero nulo. O que la implementación puede llamar al . De acuerdo con el último borrador, "El valor del primer argumento suministrado a una función de cancelación de asignación puede ser un valor de puntero nulo, si es así, y si la función de cancelación de asignación es que se suministra en la biblioteca estándar, la llamada tiene sin efecto." No estoy muy seguro de lo que las implicaciones de que "es que se suministra en la biblioteca estándar" están destinados a ser tomada literalmente ---, ya que su función no es uno provisto por la biblioteca estándar, la sentencia no parece aplicarse Pero de alguna manera, que no tiene sentido

Me recuerda esto becoz tenía un prob similares en algún momento hacia atrás y había conservado la respuesta en un archivo .txt.

ACTUALIZACIÓN-1:

Oh Me pareció here. Lea también este enlace defect report. Entonces, la respuesta es No especificado. Capítulo 5.3.5/7.

+0

+1 suena tan cerca de una respuesta como la que obtendremos ... aunque espero nuevos desarrollos con la respiración contenida. –

+0

Si tiene que ser una operación no operativa, el compilador no puede ejecutar ningún código de usuario, ya que ese código puede provocar efectos secundarios (es decir, hacer una impresión) – xtofl

3

no tengo una buena respuesta, pero he simplificado el tema ligeramente. El código siguiente quita el operador nuevo y 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 presenta el mismo comportamiento y des así sucesivamente tanto VC++ y g ++.

Por supuesto, la eliminación de un puntero NULL no es operativa, por lo que el compilador no tiene que llamar al operador delete. Si uno realmente asigna un objeto:

Widget* w = new Widget; 

entonces todo funciona como se esperaba.

-2

Usted estaba tratando de 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 nueva
Widget()
~ Widget()
operador delete

+0

Sí, pero la pregunta es, ¿por qué se llama a su eliminación personalizada cuando se llama a eliminar con NULL? (¿Pero solo si la clase no tiene destructor?) –

+1

La pregunta no es por qué no se llama al destructor. La pregunta es más bien que cuando no proporciono un destructor (en cuyo caso el compilador crea uno para mí), ¿por qué se llama a la eliminación sobrecargada? –

4

La razón 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 un destructor, el compilador simplemente llama directamente al operador de eliminación, 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.

+3

Esto no es del todo correcto: 'borrar 'obviamente no puede ser hecho desde dentro de destructor, porque un destructor sería llamado para objetos de esa clase con almacenamiento estático y automático también, y la invocación 'delete' sobre estos es obviamente indeseable. Lo que sucede en cambio es que VC++ genera una función _extra_ llamada "destructor de eliminación escalar", que primero llama al destructor propiamente dicho (que aún se genera como otra función separada) y luego realiza 'delete this'. Y luego llama a ese destructor de eliminación escalar donde quiera que 'delete' en objetos de ese tipo. –

+0

Gracias por la corrección, Pavel. Se corrigió mi respuesta en consecuencia. – eran

+0

Creo que esta es la respuesta correcta, ya que es compatible con el hecho de que si se elimina el dtor del widget, pero se agrega un objeto con dtor, obtendrá el mismo comportamiento. –

-1

El objeto destructor se llama antes del operador de eliminación. Así que mi conjetura sería que se trata de llamar al destructor, se da cuenta de que el puntero es NULL, por lo tanto

  1. no llama destructor que necesita una instancia
  2. detiene la operación deleteing allí (tipo de optimización en mi humilde opinión velocidad) .

Como dijo Neil, si w contiene un Widget, debería funcionar.

+0

Si no llama al destructor, entonces debería mostrarse el mismo comportamiento cuando no escribo un destructor (en cuyo caso el compilador debería crear uno para mí). El punto principal aquí es por qué el comportamiento es diferente en los 2 casos –

+0

Porque cuando el destructor no se especifica, el operador de eliminación se invoca directamente (comportamiento predeterminado), mientras que cuando se especifica, existe esta "comprobación previa" de la instancia Estoy diciendo aquí. Aquí estoy explicando el caso _con_ el destructor, la comparación fue implícita, aunque quizás no lo suficientemente claro. –

9

En primer lugar, esto realmente se puede simplificar hasta delete (Widget*)0 - todo lo demás en su main() es innecesario reproducir este.

Es un artefacto de generación de código que ocurre porque 1) el usuario operator delete debe ser capaz de manejar valores NULOS, y 2) el compilador intenta generar el código más óptimo posible.

Primero consideremos el caso cuando no se trata de un destructor definido por el usuario. Si ese es el caso, no hay código para ejecutar en la instancia, excepto para operator delete. No tiene sentido comprobar el valor nulo antes de transferir el control al operator delete, porque este último debería hacer un control de todos modos; y entonces el compilador genera una llamada incondicional de operator delete (y ve a este último imprimir un mensaje).

Ahora se definió el segundo caso - destructor. Esto significa que su declaración delete en realidad 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 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 omitir la llamada al operator delete - 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 de operator delete en caso de que el puntero en realidad es nulo.

Por lo que puedo ver, nada en esto está de ninguna manera garantizado por la especificación ISO C++. Es solo que ambos compiladores hacen la misma optimización aquí.

+2

¡Buena explicación! Imho, hay un tercer caso, también: destructor virtual definido. Este no se puede encontrar en tiempo de ejecución si el puntero es NULL. Otra razón más por la cual la eliminación de punteros NULL no puede llamar al código del destructor ... – xtofl

3

Me gustaría dejar un comentario, en lugar de responder, no tenía suficientes privilegios como nuevo miembro.

Se está produciendo una excepción durante la creación del objeto. El destructor no se está llamando, ya que el objeto no se crea.

Eso también se puede observar, ya que los mensajes del constructor & 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.

+1

Eso es definitivamente simple. –

+0

¡Y un buen punto también! – xtofl

Cuestiones relacionadas