2010-07-20 15 views
5

En mi opinión, el siguiente código (de alguna pregunta de C++) debería conducir a UB, pero parece que no lo es. Aquí está el código:¿Llama explícitamente el resultado de destructor en Comportamiento indefinido aquí?

#include <iostream> 
using namespace std; 
class some{ public: ~some() { cout<<"some's destructor"<<endl; } }; 
int main() { some s; s.~some(); } 

y la respuesta es:

some's destructor 
some's destructor 

aprendí forma de C++ FAQ Lite que no hay que llamar explícitamente a destructor. Creo que después de la llamada explícita al destructor, el objeto s debe ser eliminado. El programa vuelve a llamar al destructor automáticamente cuando termina, debe ser UB. Sin embargo, lo probé en g ++ y obtuve el mismo resultado que la respuesta anterior.

¿Es porque la clase es demasiado simple (no involucra nuevo/eliminar)? ¿O no es UB en absoluto en este caso?

+10

El punto del * comportamiento indefinido * es que está ** indefinido **. El hecho de que "funcione" es solo una de las infinitas posibilidades. – ereOn

+0

Este destructor es demasiado simple para tener efectos nocivos. Creo que llamar al destructor no es un caso especial, sino simplemente llamar a un método de la clase. Tiene el caso especial de ser llamado justo antes de ser desasignado (desde eliminar o salir del alcance). –

+0

@ereOn: Gracias. Entiendo que "funciona" en g ++ no significa que no esté "indefinido". Sin embargo, la respuesta en línea (que podría no ser correcta) NO es UB, es por eso que estoy confundido. – EXP0

Respuesta

15

El comportamiento no está definido porque el destructor se invoca dos veces por el mismo objeto:

  • vez cuando se invoca explícitamente
  • Una vez cuando el alcance termina y la variable automática se destruye

Invocar el destructor en un objeto cuya duración ha finalizado produce un comportamiento indefinido según C++ 03 §12.4/6:

el comportamiento es indefinido si el destructor se invoca para un objeto cuya vida útil ha terminado

curso de la vida de un objeto termina cuando su destructor se llama por §3.8/1:

El tiempo de vida de un objeto de tipo T termina cuando:

- si T es un tipo de clase con un destructor no trivial (12.4), la llamada destructor comienza, o

- el almacenamiento que ocupa el objeto se reutiliza o libera.

Tenga en cuenta que esto significa que si la clase tiene un destructor trivial, el comportamiento está bien definida debido a que el tiempo de vida de un objeto de tal tipo no termina hasta que se dio a conocer su almacenamiento, lo que para las variables automáticas no sucede hasta el final de la función. Por supuesto, no sé por qué invocarías explícitamente al destructor si es trivial.

¿Qué es un destructor trivial? §12.4/3 dice:

Un destructor es trivial si se trata de un destructor declarada implícitamente -y si:

- todas las clases base directos de su clase tienen destructores triviales y

- para todos los miembros de datos no estáticos de su clase que son del tipo de clase (o matriz de los mismos), cada clase tiene un destructor trivial.

Como han mencionado otros, un posible resultado de un comportamiento indefinido es que su programa parece seguir funcionando correctamente; otro posible resultado es su programa fallando. Cualquier cosa puede suceder y no hay garantías de ningún tipo.

+0

Gracias. No tengo el documento estándar, y me di cuenta de que tengo que pagar para obtener uno. ¿Le importaría proporcionar la definición de "destructor no trivial"? ¿El que está en la pregunta es un destructor no trivial? Gracias. – EXP0

+0

@ EXP0: agregué la definición de un destructor trivial para usted. El destructor en la pregunta no es un destructor trivial. –

+0

@JamesMcNellis, lo que dice el párrafo no es lo mismo que usted interpreta. El párrafo dice que se llama al destructor de un objeto que se ha eliminado, y la pregunta es pedir llamar al destructor * como un método normal * antes de destruirlo. Eso no es lo mismo. Llamar explícitamente al destructor no elimina el objeto. Para eliminar explícitamente el objeto, debe llamar al operador 'delete' en la instancia (y no es automático). Por cierto, nunca escuché sobre la posibilidad de llamar al destructor explícitamente, ya que debería interpretarse como el operador' ~ '. –

1

El problema aquí es que la eliminación/desasignación y los destructores son construcciones separadas e independientes. Al igual que new/allocation y constructores. Es posible hacer solo uno de los anteriores sin el otro.

En el caso general, este escenario no tiene utilidad y solo genera confusión con los valores asignados de la pila. Fuera de mi cabeza, no puedo pensar en un buen escenario en el que quieras hacer esto (aunque estoy seguro de que hay uno potencial). Sin embargo, es posible pensar en escenarios artificiales donde esto sería legal.

class StackPointer<T> { 
    T* m_pData; 
public: 
    StackPointer(T* pData) :m_pData(pData) {} 
    ~StackPointer() { 
    delete m_pData; 
    m_pData = NULL; 
    } 
    StackPointer& operator=(T* pOther) { 
    this->~StackPointer(); 
    m_pData = pOther; 
    return this; 
    } 
}; 

Nota: Por favor, nunca codifique una clase de esta manera. Tener un método de lanzamiento explícito en su lugar.

+1

El código para 'std :: vector' (entre otros) mostrará un buen usar para la invocación dtor explícita. –

+0

@Jerry, muy cierto, pero no para valores de pila que * parecen * ser el foco de la pregunta del OP. – JaredPar

+0

Sí, con esa restricción es mucho * más difícil encontrar un escenario razonable. –

1

Lo más probable es que funcione bien porque el destructor no hace referencia a ninguna variable de miembro de clase. Si intentaste delete una variable dentro del destructor, probablemente tendrías problemas cuando se llame automáticamente por segunda vez.

Por otra parte, con un comportamiento indefinido, ¿quién sabe? :)

6

Es un comportamiento indefinido, pero como con cualquier UB, una posibilidad es que (más o menos) parezca funcionar, al menos para alguna definición de trabajo.

Esencialmente, el único momento que necesita (o desea) invocar explícitamente un destructor es junto con la colocación nueva (es decir, utiliza la colocación nueva para crear un objeto en una ubicación específica y una invocación dtor explícita para destruir ese objeto)

+0

-1 Pero no está indefinido. El lenguaje permite llamadas explícitas de esta manera porque puede usarse y puede ser útil. – Elemental

+3

@Elemental: una invocación de dtor explícita no es UB en sí misma, pero destruir el mismo objeto dos veces más seguro ** es ** UB. De hecho, este es un ejemplo dado en el estándar (§12.4/14): "Una vez que se invoca un destructor para un objeto, el objeto ya no existe; el comportamiento no está definido si se invoca el destructor para un objeto cuya vida útil ha terminado (3.8). [Ejemplo: si el destructor para un objeto automático se invoca explícitamente, y el bloque se deja posteriormente de una manera que ordinariamente invocaría la destrucción implícita del objeto, el comportamiento no está definido.] " –

0

Lo que hace la función principal es reservar espacio en la pila, llamar al constructor de alguno y, al final, llamar al destructor de alguno. Esto siempre ocurre con una variable local, sea cual sea el código que coloque dentro de la función. Su compilador no detectará que llamó manualmente al destructor.

De todos modos, nunca debe llamar manualmente al destructor de un objeto, excepto los objetos creados con la ubicación-nueva.

3

De http://www.devx.com/tips/Tip/12684

comportamiento indefinido indica que una aplicación puede comportarse de forma impredecible cuando un programa llega a un cierto estado, que casi sin excepción es el resultado de un error. El comportamiento indefinido se puede manifestar como un bloqueo de tiempo de ejecución, un estado de programa inestable y poco fiable o, en casos raros, incluso puede pasar inadvertido.

En su caso, no falla porque el destructor no manipula ningún campo; de hecho, su clase no tiene ningún miembro de datos en absoluto. Si lo hizo y en el cuerpo del destructor lo manipulaste de cualquier manera, es probable que recibas una excepción en tiempo de ejecución mientras llamas al destructor por segunda vez.

0

Creo que si quiere que su código esté bien, simplemente necesita llamar a la ubicación nueva y volver a llenarlo antes de salir. La llamada al destructor no es el problema, es la segunda llamada al destructor que se realiza cuando abandonas el alcance.

0

¿Puede definir el comportamiento indefinido que espera?Indefinido no significa aleatorio (o catastrófico): el comportamiento de un programa dado puede repetirse entre invocaciones, simplemente significa que no se puede CONFIAR en ningún comportamiento en particular porque no está definido y no hay garantía de lo que sucederá.

0

Es un comportamiento indefinido. El comportamiento indefinido es la llamada al doble destructor y no con la llamada al destructor en sí. Si modifica su ejemplo a:

#include <iostream> 
using namespace std; 
class some{ public: ~some() { [INSERT ANY CODE HERE] } }; 
int main() { some s; s.~some(); } 

donde [insertar cualquier código aquí] se puede reemplazar con cualquier código arbitrario. Los resultados tienen efectos secundarios impredecibles, por lo que se considera indefinido.

Cuestiones relacionadas