2009-07-09 11 views
13

Estoy empezando con RAII en C++ y establecí un pequeño caso de prueba. O mi código está muy confundido, ¡o RAII no funciona! (Supongo que es el primero).C++ RAII no funciona?

Si me quedo:

#include <exception> 
#include <iostream> 
class A { 
public: 
    A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
    ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
private: 
    int i_; 
}; 

int main(void) { 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
    return 0; 
} 

con la excepción comentada me sale:

A 1 constructed 
A 2 constructed 
A 2 destructed 
A 1 destructed 

como se esperaba, pero con la excepción consigo:

A 1 constructed 
A 2 constructed 
terminate called after throwing an instance of 'std::exception' 
    what(): std::exception 
Aborted 

así que mis objetos no son destruidos a pesar de que están fuera del alcance. ¿No es esta la base completa de RAII?

Punteros y correcciones, ¡muy apreciadas!

+5

¡Usted también encontró un error en C++! =) – Eric

+0

interesante borde caso! –

+2

Has roto RAII :( – rpg

Respuesta

6

Tiene una excepción sin manos en el principal, lo que significa una llamada para terminar. Intente esto:

int main(void) 
{ 
    try 
    { 
     A a1(1); 
     A a2(2); 
     throw std::exception(); 
     return 0; 
    } 
    catch(const std::exception & e) 
    { 
     return 1; 
    } 


} 
+0

Así que siempre debo ajustar main en try/catch cuando utilizo RAII? – John

+2

Creo que main() es diferente. Intenta poner todo en una función a la que llama desde main(). –

+1

No, solo cuando hace algo que puede arrojar una excepción. – Alex

3

No está manejando la excepción correctamente, por lo que su aplicación se está saliendo antes de que los objetos salgan del alcance.

Voy a explicar un poco más. Si una excepción "burbujea" hasta la posición principal, la pila se desenrolla (edita). Incluso mover el código a una función secundaria no solucionará este problema. por ejemplo:

 1 #include <exception> 
     2 #include <iostream> 
     3 
     4 void test(); 
     5 
     6 class A { 
     7  public: 
     8   A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
     9   ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
    10  private: int i_; 
    11 }; 
    12 
    13 
    14 int main(void) { 
    15  test(); 
    16  return 0; 
    17 } 
    18 
    19 void test(){ 
    20    A a1(1); 
    21    A a2(2); 
    22   throw std::exception(); 
    23 } 

El código anterior no se resolver el problema. La única forma de resolver esto es envolver la excepción lanzada en un bloque try-catch. Esto evitará que la excepción llegue al principal y detendrá la terminación que está ocurriendo antes de que los objetos salgan del alcance.

+0

Esta es una respuesta correcta, -1? De Verdad? – Alex

+0

Incluso si mueve el código a otra función si no tiene una try/catch, saltará al principal y causará el mismo problema. Entonces "principal" no es especial en absoluto. – Alex

+0

Estás equivocado sobre el "comportamiento indefinido". El comportamiento de una excepción sin controlador se define en el Estándar, excepto que si la pila se desenrolla o no se define la implementación. –

20

No tiene un controlador para su excepción. Cuando esto sucede, el estándar dice que se llama std :: terminate, que a su vez llama abortar. Consulte la sección 14.7 en The C++ Programming Language, 3ra edición.

+0

En realidad puede establecer su propia función de finalización, pero no sé si es una función realmente útil. –

+2

Bueno, podría usarse, por ejemplo, para registrar la terminación en un archivo específico o enviar un correo electrónico a alguien. –

+0

Como señaló RaphaelPS, escribir su propia función de finalización es útil si desea registrar la terminación o la limpieza de los recursos globales. Sin embargo, no puede recuperarse de la terminación. Un manejador de terminación no toma argumentos, no tiene valor de retorno y se espera que salga de la aplicación. –

17

El problema es que main tiene un estado especial. Cuando se lanza una excepción desde allí, la pila no se puede desenrollar de manera significativa, la aplicación simplemente llama al std:terminate.

Y luego tiene un poco de sentido por qué las variables no salen de su alcance. No hemos abandonado el ámbito en el que se declararon. Lo que sucede podría ser considerado como equivalente a esto:

int main(void) { 
    A a1(1); 
    A a2(2); 
    std::terminate(); 
} 

(creo que se define a la ejecución si destructores son llamados en este caso, sin embargo, por lo que en algunas plataformas, que funcionará como se esperaba)

+0

¿Por qué se define esa implementación? ¿No tendría más sentido si siempre se llama a los constructores? C++ puede ser tonto a veces ... – Zifre

+2

No es tonto - práctico. Obligar a los destructores (no a los constructores) a que se llamen siempre limitaría las formas en que se puede implementar EH y desenrollar las pilas. –

+3

Puede ser tonto, pero también es pragmático. La especificación C++ trata de evitar la limitación de implementadores. Idealmente, quieren que sea posible crear una implementación conforme a los estándares de C++ en * cualquier * CPU y * cualquier * sistema operativo. Y dado que la función principal en cierto sentido marca el límite entre el sistema operativo y C++, no quieren asumir demasiado al respecto. Tienes razón, en el mundo real, sería bueno que no dejaran este detalle en particular para la implementación. Ahora lo sabemos, pero ¿era tan obvio en 1998, cuando el lenguaje estaba siendo estandarizado? No querían pintarse en una esquina – jalf

5

Como han señalado otros, tiene una excepción no detectada, que llama a terminate(). Está definido por implementación (ver el estándar, 15.3 párrafo 9 y 15.5.1 párrafo 2) si se llaman destructores en este caso, y la definición en su implementación es aparentemente "No, no lo harán". (Si se llama a terminate() por cualquier otra razón que no sea lanzar una excepción que no tenga un controlador, no se invocarán destructores).

4

Sus objetos A no se destruyen porque se está llamando a std :: terminate.

std :: terminate se invoca cuando una excepción no controlada se escapa del principal. Si ajusta su código en una prueba/captura (incluso si la captura simplemente vuelve a subir) verá el comportamiento que esperaba.

6

Si una excepción se escapa de main() es tiempo de implementación definida la pila se desenrolla.

tratar

int main() 
{ 
    try 
    { 
     doWork(); // Do you experiment here. 
    } 
    catch(...) 
    { /* 
     * By catching here you force the stack to unwind correctly. 
     */ 
     throw; // re-throw so exceptions pass to the OS for debugging. 
    } 
} 
0

Dado que la excepción no es manejado por el momento en que llega principal(), que se traduce en una llamada a std :: terminar(), que esencialmente tiene el equivalente de

int main(void) { 
    A a1(1); 
    A a2(2); 
    exit(1); 
} 

No se garantiza que se llame a los destruidores en los casos en que el programa finaliza antes de que salgan del alcance. Por otro agujero en RAII, consideran:

int main(void) { 
    A *a1 = new A(1); 
} 
+1

El segundo ejemplo no es un agujero en RAII; es un ejemplo de no usar RAII. –

1

Otros han sugerido poner un try/catch dentro main() para manejar esto, que funciona muy bien. Por alguna razón, me parece que el 'function-try-block' raramente utilizado se ve mejor, lo cual me sorprende (pensé que se vería muy raro). Pero no creo que haya ninguna ventaja real:

int main(void) 
try 
{ 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
    return 0; 
} 
catch (...) 
{ 
    throw; 
} 

Un par de desventajas son que ya que rara vez se utiliza una gran cantidad de desarrolladores son lanzados para un bucle cuando lo ven, y VC6 ahoga en él si eso es una consideración.

0

El siguiente código funciona.

#include <exception> 
#include <iostream> 

class A { 
public: 
    A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
    ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
private: 
    int i_; 
}; 

void test() { 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
} 

int main(void) { 
try { 
    test(); 
} catch(...) { 
} 
    return 0; 
}