¿C ++ admite bloques “finalmente”? (¿Y de qué se trata este ‘RAII’?)

¿C ++ admite bloques ” finalmente “?

¿Cuál es el modismo de RAII ?

¿Cuál es la diferencia entre el modismo RAII de C ++ y el enunciado ‘using’ de C # ?

No, C ++ no admite bloques ‘finalmente’. La razón es que C ++ en cambio admite RAII: “Adquisición de recursos es inicialización”: un nombre deficiente para un concepto realmente útil.

La idea es que el destructor de un objeto es responsable de liberar recursos. Cuando el objeto tiene una duración de almacenamiento automática, se llamará al destructor del objeto cuando salga el bloque en el que se creó, incluso cuando ese bloque se cierre en presencia de una excepción. Aquí está la explicación de Bjarne Stroustrup sobre el tema.

Un uso común para RAII es bloquear un mutex:

// A class with implements RAII class lock { mutex &m_; public: lock(mutex &m) : m_(m) { m.acquire(); } ~lock() { m_.release(); } }; // A class which uses 'mutex' and 'lock' objects class foo { mutex mutex_; // mutex for locking 'foo' object public: void bar() { lock scopeLock(mutex_); // lock object. foobar(); // an operation which may throw an exception // scopeLock will be destructed even if an exception // occurs, which will release the mutex and allow // other functions to lock the object and run. } }; 

RAII también simplifica el uso de objetos como miembros de otras clases. Cuando se destruye la clase propietaria, se libera el recurso administrado por la clase RAII porque se llama al destructor para la clase administrada por RAII como resultado. Esto significa que cuando usa RAII para todos los miembros de una clase que administra recursos, puede usar un destructor muy simple, incluso el predeterminado, para la clase propietaria, ya que no necesita administrar manualmente los tiempos de vida de sus recursos miembro. . (Gracias a Mike B por señalar esto).

Para aquellos que conocen a C # o VB.NET, pueden reconocer que RAII es similar a la destrucción determinística de .NET usando declaraciones IDisposables y “usando” . De hecho, los dos métodos son muy similares. La principal diferencia es que RAII liberará de manera determinista cualquier tipo de recurso, incluida la memoria. Al implementar IDisposable en .NET (incluso el lenguaje .NET C ++ / CLI), los recursos se liberarán de forma determinística, excepto en el caso de la memoria. En .NET, la memoria no se libera de forma determinista; la memoria solo se libera durante los ciclos de recolección de basura.

† Algunas personas creen que “Destrucción es la renuncia a los recursos” es un nombre más preciso para el modismo de RAII.

En C ++, finalmente NO es necesario debido a RAII.

RAII transfiere la responsabilidad de la seguridad de excepciones del usuario del objeto al diseñador (y ejecutor) del objeto. Yo diría que este es el lugar correcto ya que solo necesita obtener una excepción de seguridad correcta una vez (en el diseño / implementación). Al usarlo finalmente, es necesario que la seguridad de excepción sea correcta cada vez que use un objeto.

También IMO el código se ve más ordenado (ver a continuación).

Ejemplo:

Un objeto de base de datos. Para asegurarse de que se usa la conexión DB, debe abrirse y cerrarse. Al usar RAII esto se puede hacer en el constructor / destructor.

C ++ como RAII

 void someFunc() { DB db("DBDesciptionString"); // Use the db object. } // db goes out of scope and destructor closes the connection. // This happens even in the presence of exceptions. 

El uso de RAII hace que el uso de un objeto DB sea muy fácil. El objeto DB se cerrará correctamente mediante el uso de un destructor, sin importar cómo intentemos abusarlo.

Java como finalmente

 void someFunc() { DB db = new DB("DBDesciptionString"); try { // Use the db object. } finally { // Can not rely on finaliser. // So we must explicitly close the connection. try { db.close(); } catch(Throwable e) { /* Ignore */ // Make sure not to throw exception if one is already propagating. } } } 

Cuando se usa finalmente, el uso correcto del objeto se delega al usuario del objeto. es decir , es responsabilidad del usuario del objeto cerrar de manera correcta y explícita la conexión DB. Ahora podría argumentar que esto se puede hacer en el finalizador, pero los recursos pueden tener disponibilidad limitada u otras restricciones y, por lo tanto, generalmente desea controlar el lanzamiento del objeto y no confiar en el comportamiento no determinista del recolector de basura.

También este es un ejemplo simple.
Cuando tiene que liberar varios recursos, el código puede complicarse.

Puede encontrar un análisis más detallado aquí: http://accu.org/index.php/journals/236

En C ++ 11, si es necesario, RAII permite hacer finalmente:

 namespace detail { //adapt to your "private" namespace template  struct FinalAction { FinalAction(F f) : clean_{f} {} ~FinalAction() { if(enabled_) clean_(); } void disable() { enabled_ = false; }; private: F clean_; bool enabled_{true}; }; } template  detail::FinalAction finally(F f) { return detail::FinalAction(f); } 

ejemplo de uso:

 #include  int main() { int* a = new int; auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; }); std::cout << "doing something ...\n"; } 

la salida será:

 doing something... leaving the block, deleting a! 

Personalmente, utilicé estas pocas veces para asegurarme de cerrar el descriptor de archivo POSIX en un progtwig C ++.

Tener una clase real que administre recursos y evite cualquier tipo de fuga suele ser mejor, pero finalmente es útil en los casos en los que hacer una clase suena como un exceso.

Además, me gusta más que otros lenguajes finalmente porque si se usa de forma natural, escribes el código de cierre cerca del código de apertura (en mi ejemplo, el nuevo y eliminar ) y la destrucción sigue la construcción en orden LIFO como es habitual en C ++. El único inconveniente es que obtienes una variable automática que no usas realmente y la syntax lambda lo hace un poco ruidoso (en mi ejemplo, en la cuarta línea solo la palabra finalmente y el {} -bloque de la derecha son significativos, el descanso es esencialmente ruido).

Otro ejemplo:

  [...] auto precision = std::cout.precision(); auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } ); std::cout << std::setprecision(3); 

El miembro deshabilitado es útil si finalmente se debe llamar solo en caso de falla. Por ejemplo, debe copiar un objeto en tres contenedores diferentes, puede configurar finalmente para deshacer cada copia y deshabilitar después de que todas las copias sean exitosas. Si lo haces, si la destrucción no puede tirar, te aseguras una garantía sólida.

desactivar ejemplo:

 //strong guarantee void copy_to_all(BIGobj const& a) { first_.push_back(a); auto undo_first_push = finally([first_&] { first_.pop_back(); }); second_.push_back(a); auto undo_second_push = finally([second_&] { second_.pop_back(); }); third_.push_back(a); //no necessary, put just to make easier to add containers in the future auto undo_third_push = finally([third_&] { third_.pop_back(); }); undo_first_push.disable(); undo_second_push.disable(); undo_third_push.disable(); } 

Más allá de facilitar la limpieza con objetos basados ​​en stack, RAII también es útil porque la misma limpieza “automática” ocurre cuando el objeto es miembro de otra clase. Cuando se destruye la clase propietaria, el recurso administrado por la clase RAII se limpia porque se llama al dtor de esa clase como resultado.

Esto significa que cuando llegue al nirvana de RAII y todos los miembros de una clase usen RAII (como punteros inteligentes), puede salirse con la suya con un dtor muy simple (tal vez incluso predeterminado) ya que no necesita administrarlo manualmente. tiempos de vida de los recursos de los miembros

¿Por qué es que incluso los lenguajes administrados proporcionan un bloque finalmente a pesar de que el recolector de basura desasigna automáticamente los recursos?

En realidad, los idiomas basados ​​en recolectores de basura necesitan “finalmente” más. Un recolector de basura no destruye sus objetos de manera oportuna, por lo que no se puede confiar en que borre los problemas no relacionados con la memoria correctamente.

En términos de datos asignados dinámicamente, muchos argumentarían que debería estar usando punteros inteligentes.

Sin embargo…

RAII transfiere la responsabilidad de la seguridad de excepción del usuario del objeto al diseñador

Lamentablemente, esta es su propia caída. Los viejos hábitos de progtwigción C se mueren duro. Cuando usa una biblioteca escrita en C o en un estilo muy C, RAII no se habrá usado. En lugar de volver a escribir todo el front-end de API, es exactamente con lo que tienes que trabajar. Entonces la falta de “finalmente” realmente muerde.

Lo siento por desenterrar un hilo tan viejo, pero hay un gran error en el siguiente razonamiento:

RAII transfiere la responsabilidad de la seguridad de excepciones del usuario del objeto al diseñador (y ejecutor) del objeto. Yo diría que este es el lugar correcto ya que solo necesita obtener una excepción de seguridad correcta una vez (en el diseño / implementación). Al usarlo finalmente, es necesario que la seguridad de excepción sea correcta cada vez que use un objeto.

En la mayoría de los casos, debe tratar con objetos dinámicamente asignados, números dynamics de objetos, etc. Dentro del bloque try, algunos códigos pueden crear muchos objetos (cuántos se determinan en tiempo de ejecución) y almacenarlos en una lista. Ahora bien, este no es un escenario exótico, sino muy común. En este caso, querrías escribir cosas como

 void DoStuff(vector input) { list myList; try { for (int i = 0; i < input.size(); ++i) { Foo* tmp = new Foo(input[i]); if (!tmp) throw; myList.push_back(tmp); } DoSomeStuff(myList); } finally { while (!myList.empty()) { delete myList.back(); myList.pop_back(); } } } 

Por supuesto, la lista en sí se destruirá cuando salga del scope, pero eso no limpiaría los objetos temporales que ha creado.

En cambio, tienes que tomar la ruta fea:

 void DoStuff(vector input) { list myList; try { for (int i = 0; i < input.size(); ++i) { Foo* tmp = new Foo(input[i]); if (!tmp) throw; myList.push_back(tmp); } DoSomeStuff(myList); } catch(...) { } while (!myList.empty()) { delete myList.back(); myList.pop_back(); } } 

Además: ¿por qué es que incluso los lenguajes administrados proporcionan un bloque finalmente a pesar de que los recursos son desasignados automáticamente por el recolector de basura de todos modos?

Sugerencia: hay más cosas que puedes hacer con "finalmente" que simplemente desasignación de memoria.

FWIW, Microsoft Visual C ++ sí es compatible con try, finalmente, y se ha usado históricamente en aplicaciones MFC como un método para detectar excepciones serias que de lo contrario resultarían en un locking. Por ejemplo;

 int CMyApp::Run() { __try { int i = CWinApp::Run(); m_Exitok = MAGIC_EXIT_NO; return i; } __finally { if (m_Exitok != MAGIC_EXIT_NO) FaultHandler(); } } 

He usado esto en el pasado para hacer cosas como guardar copias de seguridad de archivos abiertos antes de salir. Sin embargo, ciertas configuraciones de depuración JIT romperán este mecanismo.

En realidad no, pero puedes emularlos en cierta medida, por ejemplo:

 int * array = new int[10000000]; try { // Some code that can throw exceptions // ... throw std::exception(); // ... } catch (...) { // The finally-block (if an exception is thrown) delete[] array; // re-throw the exception. throw; } // The finally-block (if no exception was thrown) delete[] array; 

Tenga en cuenta que el bloque finally podría lanzar una excepción antes de que se vuelva a lanzar la excepción original, descartando así la excepción original. Este es exactamente el mismo comportamiento que en un bloque finalmente Java. Además, no puede usar return dentro de los bloques try & catch.

Se me ocurrió una macro finally que se puede usar casi como ¹ la palabra clave finally en Java; hace uso de std::exception_ptr y friends, funciones lambda y std::promise , por lo que requiere C++11 o superior; también hace uso de la expresión compuesta expresión GCC, que también es compatible con clang.

ADVERTENCIA : una versión anterior de esta respuesta utilizó una implementación diferente del concepto con muchas más limitaciones.

Primero, definamos una clase de ayuda.

 #include  template  class FinallyHelper { template  struct TypeWrapper {}; using Return = typename std::result_of::type; public: FinallyHelper(Fun body) { try { execute(TypeWrapper(), body); } catch(...) { m_promise.set_exception(std::current_exception()); } } Return get() { return m_promise.get_future().get(); } private: template  void execute(T, Fun body) { m_promise.set_value(body()); } void execute(TypeWrapper, Fun body) { body(); } std::promise m_promise; }; template  FinallyHelper make_finally_helper(Fun body) { return FinallyHelper(body); } 

Luego está la macro real.

 #define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try #define finally }); \ true; \ ({return __finally_helper.get();})) \ /***/ 

Se puede usar así:

 void test() { try_with_finally { raise_exception(); } catch(const my_exception1&) { /*...*/ } catch(const my_exception2&) { /*...*/ } finally { clean_it_all_up(); } } 

El uso de std::promise hace que sea muy fácil de implementar, pero probablemente también introduce una sobrecarga innecesaria que podría evitarse mediante la reimplementación de las funciones necesarias de std::promise .


¹ CAVEAT: hay algunas cosas que no funcionan como la versión de java de finally . La parte superior de mi cabeza:

  1. no es posible romper desde un bucle externo con la instrucción break desde dentro de los bloques try y catch() , ya que viven dentro de una función lambda;
  2. debe haber al menos un bloque catch() después de la try : es un requisito de C ++;
  3. si la función tiene un valor de retorno distinto de void pero no hay retorno dentro de los bloques try y catch()'s , la comstackción fallará porque la macro finally se expandirá al código que querrá devolver un void . Esto podría ser, err, un vacío ed teniendo una macro de tipo finally_noreturn .

En general, no sé si alguna vez usaría esto, pero fue divertido jugar con eso. 🙂

Tengo un caso de uso donde creo que finally debería ser una parte perfectamente aceptable del lenguaje C ++ 11, ya que creo que es más fácil de leer desde el punto de vista del flujo. Mi caso de uso es una cadena de hilos de consumidor / productor, donde se envía un nullptr centinela al final de la ejecución para cerrar todos los hilos.

Si C ++ lo admite, querrá que su código se vea así:

  extern Queue downstream, upstream; int Example() { try { while(!ExitRequested()) { X* x = upstream.pop(); if (!x) break; x->doSomething(); downstream.push(x); } } finally { downstream.push(nullptr); } } 

Creo que esto es más lógico que poner tu statement final al principio del ciclo, dado que ocurre después de que el ciclo ha salido … pero eso es una ilusión porque no podemos hacerlo en C ++. Tenga en cuenta que la cola de cola está conectada a otra cadena de caracteres, por lo que no puede colocar la push(nullptr) centinela push(nullptr) en el destructor de la downstream porque no se puede destruir en este punto … necesita mantenerse activa hasta que la otra thread recibe el nullptr .

Así que aquí está cómo usar una clase RAII con lambda para hacer lo mismo:

  class Finally { public: Finally(std::function callback) : callback_(callback) { } ~Finally() { callback_(); } std::function callback_; }; 

y así es como lo usas:

  extern Queue downstream, upstream; int Example() { Finally atEnd([](){ downstream.push(nullptr); }); while(!ExitRequested()) { X* x = upstream.pop(); if (!x) break; x->doSomething(); downstream.push(x); } } 

Otra emulación de bloques “finalmente” usando C ++ 11 funciones lambda

 template  inline void with_finally(const TCode &code, const TFinallyCode &finally_code) { try { code(); } catch (...) { try { finally_code(); } catch (...) // Maybe stupid check that finally_code mustn't throw. { std::terminate(); } throw; } finally_code(); } 

Esperemos que el comstackdor optimice el código anterior.

Ahora podemos escribir código como este:

 with_finally( [&]() { try { // Doing some stuff that may throw an exception } catch (const exception1 &) { // Handling first class of exceptions } catch (const exception2 &) { // Handling another class of exceptions } // Some classes of exceptions can be still unhandled }, [&]() // finally { // This code will be executed in all three cases: // 1) exception was not thrown at all // 2) exception was handled by one of the "catch" blocks above // 3) exception was not handled by any of the "catch" block above } ); 

Si lo desea, puede ajustar este modismo en macros “try-finally”:

 // Please never throw exception below. It is needed to avoid a comstacktion error // in the case when we use "begin_try ... finally" without any "catch" block. class never_thrown_exception {}; #define begin_try with_finally([&](){ try #define finally catch(never_thrown_exception){throw;} },[&]() #define end_try ) // sorry for "pascalish" style :( 

Ahora el bloque “finalmente” está disponible en C ++ 11:

 begin_try { // A code that may throw } catch (const some_exception &) { // Handling some exceptions } finally { // A code that is always executed } end_try; // Sorry again for this ugly thing 

Personalmente, no me gusta la versión “macro” del modismo “finally” y preferiría usar la función pura “with_finally” aunque la syntax sea más voluminosa en ese caso.

Puede probar el código aquí arriba: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

Como mucha gente ha declarado, la solución es usar las características de C ++ 11 para evitar finalmente los bloques. Una de las características es unique_ptr .

Aquí está la respuesta de Mephane escrita usando patrones RAII.

 #include  #include  #include  using namespace std; class Foo { ... }; void DoStuff(vector input) { list > myList; for (int i = 0; i < input.size(); ++i) { myList.push_back(unique_ptr(new Foo(input[i]))); } DoSomeStuff(myList); } 

Más información sobre el uso de unique_ptr con contenedores de la Biblioteca Estándar de C ++ está aquí

Me gustaría brindar una alternativa.

Si quiere que finalmente se llame al bloque, simplemente póngalo después del último bloque catch (que probablemente debería ser catch( ... ) para capturar la excepción no conocida)

 try{ // something that might throw exception } catch( ... ){ // what to do with uknown exception } //final code to be called always, //don't forget that it might throw some exception too doSomeCleanUp(); 

Si finalmente desea bloquear como última cosa que hacer cuando se lanza cualquier excepción, puede usar la variable local booleana: antes de ejecutar, configúrelo en falso y coloque la asignación verdadera al final del bloque de prueba, luego, después de la verificación del bloque catch para la variable valor:

 bool generalAppState = false; try{ // something that might throw exception //the very end of try block: generalAppState = true; } catch( ... ){ // what to do with uknown exception } //final code to be called only when exception was thrown, //don't forget that it might throw some exception too if( !generalAppState ){ doSomeCleanUpOfDirtyEnd(); } //final code to be called only when no exception is thrown //don't forget that it might throw some exception too else{ cleanEnd(); } 

Como se señaló en las otras respuestas, C ++ puede soportar la funcionalidad final similar. La implementación de esta funcionalidad que probablemente esté más cerca de ser parte del lenguaje estándar es la que acompaña a las Directrices básicas de C ++ , un conjunto de mejores prácticas para usar C ++ editado por Bjarne Stoustrup y Herb Sutter. Esta implementación de finally es parte de la Biblioteca de soporte de directrices (GSL). A lo largo de las Directrices, se recomienda el uso de finally cuando se trata de interfaces de estilo antiguo, y también tiene una guía propia, titulada Use a final_action object para express la limpieza si no hay disponible un recurso adecuado .

Por lo tanto, no solo el soporte de C ++ finally , en realidad se recomienda su uso en muchos casos de uso comunes.

Un ejemplo de uso de la implementación de GSL sería:

 #include  void example() { int *p = get_some_resource(); auto p_clean = gsl::finally([&p] { clean_that_resource(p); }); // Do a lot of stuff, return early and throw exceptions. // clean_that_resource will always get called. } 

La implementación y el uso de GSL es muy similar al de la respuesta de Paolo.Bolzoni . Una diferencia es que el objeto creado por gsl::finally() carece de la llamada de disable() . Si necesita esa funcionalidad, puede preferir la implementación de Paolo. De lo contrario, usar GSL es lo más parecido posible al uso de funciones estandarizadas.

 try { ... goto finally; } catch(...) { ... goto finally; } finally: { ... }