¿C ++ admite bloques 'finalmente'? (¿Y de qué habla este 'RAII'?)


Respuestas:


273

No, C ++ no admite bloques 'finalmente'. La razón es que C ++ en su lugar admite RAII: "La adquisición de recursos es la inicialización", un nombre pobre 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 salga en presencia de una excepción. Aquí está la explicación del tema por Bjarne Stroustrup .

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, el recurso administrado por la clase RAII se libera porque el destructor para la clase administrada por RAII se invoca como resultado. Esto significa que cuando usa RAII para todos los miembros en una clase que administra recursos, puede salirse con la suya usando un destructor muy simple, tal vez incluso el predeterminado, para la clase propietaria, ya que no necesita administrar manualmente la vida útil de los recursos de sus miembros. . (Gracias a Mike B por señalar esto).

Para aquellos que están familiarizados con C # o VB.NET, puede reconocer que RAII es similar a la destrucción determinista de .NET usando las declaraciones IDisposable y 'using' . 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 determinista, excepto para la memoria. En .NET, la memoria no se libera de manera determinista; la memoria solo se libera durante los ciclos de recolección de basura.

 

† Algunas personas creen que "La destrucción es la renuncia a los recursos" es un nombre más exacto para el lenguaje RAII.


18
"La destrucción es la renuncia a los recursos" - DIRR ... No, no funciona para mí. = P
Erik Forbes

14
RAII está atascado, realmente no hay cambio. Intentar hacerlo sería una tontería. Sin embargo, debe admitir que "Adquisición de recursos es inicialización" sigue siendo un nombre bastante pobre.
Kevin

162
SBRM == Gestión de recursos vinculados al alcance
Johannes Schaub - litb

10
Cualquier persona con la habilidad de diseñar no solo software en general, y mucho menos técnicas mejoradas, no puede dar una excusa digna para un acrónimo tan horrendo.
Hardryv

54
Esto lo deja atascado cuando tiene algo que limpiar que no coincide con la vida útil de ningún objeto C ++. Supongo que terminas con Lifetime Equals C ++ Class Liftime o si no se pone feo (¿LECCLEOEIGU?).
Warren P

79

En C ++, finalmente NO se requiere debido a RAII.

RAII traslada la responsabilidad de la seguridad de excepción del usuario del objeto al diseñador (e implementador) del objeto. 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 usar finalmente, debe obtener una seguridad de excepción correcta cada vez que use un objeto.

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

Ejemplo:

Un objeto de base de datos. Para asegurarse de que se utiliza 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 usar un objeto DB correctamente sea muy fácil. El objeto DB se cerrará correctamente mediante el uso de un destructor, sin importar cómo intentemos abusar de él.

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 correctamente la conexión de base de datos. 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 la liberación del objeto y no confiar en el comportamiento no determinista del recolector de basura.

También este es un ejemplo simple.
Cuando tiene múltiples recursos que necesitan ser liberados, el código puede complicarse.

Un análisis más detallado se puede encontrar aquí: http://accu.org/index.php/journals/236


16
// Make sure not to throw exception if one is already propagating.Es importante que los destructores de C ++ no arrojen excepciones también por esta misma razón.
Cemafor

10
@Cemafor: La razón por la que C ++ no arroja excepciones del destructor es diferente a Java. En Java funcionará (solo pierde la excepción original). En C ++ es realmente malo. Pero el punto en C ++ es que solo tiene que hacerlo una vez (por el diseñador de la clase) cuando escribe el destructor. En Java tienes que hacerlo en el punto de uso. Por lo tanto, es responsabilidad del usuario de la clase escribir la misma placa de caldera muy a tiempo.
Martin York

1
Si se trata de ser "necesario", tampoco necesita RAII. ¡Vamos a deshacernos de eso! :-) Bromas aparte, RAII está bien para muchos casos. Lo que RAII hace más engorroso son los casos en que desea ejecutar algún código (no relacionado con los recursos) incluso si el código anterior regresó antes. Para eso, usa gotos o lo separa en dos métodos.
Trinidad

1
@Trinidad: no es tan simple como piensas (ya que todas tus sugerencias parecen elegir las peores opciones posibles). Es por eso que una pregunta puede ser un mejor lugar para explorar esto que los comentarios.
Martin York

1
Criticar el "NO es necesario debido a RAII": hay muchos casos en los que agregar RAII ad-hoc sería demasiado código repetitivo para agregar, y probar-finalmente sería extremadamente apropiado.
ceztko

63

RAII es generalmente mejor, pero usted puede fácilmente tener los finalmente la semántica en C ++. Usando una pequeña cantidad de código.

Además, las Pautas principales de C ++ dan finalmente.

Aquí hay un enlace a la implementación de GSL Microsoft y un enlace a la implementación de Martin Moene

Bjarne Stroustrup dijo varias veces que todo lo que está en la GSL significa que eventualmente entrará en el estándar. Por lo tanto, debería ser una forma a prueba de futuro para usar finalmente .

Sin embargo, puede implementarse fácilmente si lo desea, continúe leyendo.

En C ++ 11 RAII y lambdas permite hacer un general finalmente:

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

template <typename F>
detail::FinalAction<F> finally(F f) {
    return detail::FinalAction<F>(f); }

ejemplo de uso:

#include <iostream>
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, lo usé varias veces para asegurarme de cerrar el descriptor de archivo POSIX en un programa C ++.

Por lo general, es mejor tener una clase real que administre recursos y evite cualquier tipo de fugas, pero esto finalmente es útil en los casos en que hacer que una clase suene como una exageración.

Además, finalmente me gusta más que otros idiomas porque si se usa de forma natural, escribe el código de cierre cerca del código de apertura (en mi ejemplo, el nuevo y el borrado ) y la destrucción sigue a la construcción en orden LIFO como es habitual en C ++. El único inconveniente es que obtienes una variable automática que realmente no usas y la sintaxis lambda lo hace un poco ruidoso (en mi ejemplo en la cuarta línea, solo la palabra finalmente y el bloque {} a la derecha son significativos, el 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 deshabilitarla después de que todas las copias sean exitosas. Si lo hace, si la destrucción no puede lanzar, se asegura la garantía fuerte.

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(); }

Si no puede usar C ++ 11, todavía puede tenerlo finalmente , pero el código se vuelve un poco más largo. Simplemente defina una estructura con solo un constructor y un destructor, el constructor toma referencias a todo lo necesario y el destructor realiza las acciones que necesita. Esto es básicamente lo que hace el lambda, hecho manualmente.

#include <iostream>
int main() {
    int* a = new int;

    struct Delete_a_t {
        Delete_a_t(int* p) : p_(p) {}
       ~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
        int* p_;
    } delete_a(a);

    std::cout << "doing something ...\n"; }

Puede haber un posible problema: en la función 'finalmente (F f)' devuelve un objeto de FinalAction, por lo que se puede llamar al deconstructor antes de devolver finalmente la función. Tal vez deberíamos usar std :: function en lugar de la plantilla F.
user1633272

Tenga en cuenta que FinalActiones básicamente lo mismo que el ScopeGuardidioma popular , solo que con un nombre diferente.
anderas

1
¿Es segura esta optimización?
Nulano

2
@ Paolo.Bolzoni Lo siento por no responder antes, no recibí una notificación para tu comentario. Me preocupaba que el último bloque (en el que llamo una función DLL) se llamaría antes del final del alcance (porque la variable no se usa), pero desde entonces encontré una pregunta sobre SO que borró mis preocupaciones. Me vincularía a él, pero desafortunadamente, ya no puedo encontrarlo.
Nulano

1
La función disable () es una especie de verruga en su diseño limpio. Si desea que finalmente se llame solo en caso de falla, ¿por qué no usar la instrucción catch? ¿No es eso para lo que es?
user2445507

32

Más allá de facilitar la limpieza con objetos basados ​​en pila, 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 el dtor para esa clase se llama como resultado.

Esto significa que cuando alcanza el nirvana RAII y todos los miembros de una clase usan RAII (como punteros inteligentes), puede salirse con un dtor muy simple (tal vez incluso predeterminado) para la clase propietaria, ya que no necesita administrar manualmente su duración de los recursos de los miembros.


Ese es un muy buen punto. +1 a ti Sin embargo, no muchas otras personas te han votado. Espero que no te importe que haya editado mi publicación para incluir tus comentarios. (Te di crédito por supuesto.) ¡Gracias! :)
Kevin

30

¿por qué incluso los lenguajes administrados proporcionan un bloqueo final a pesar de que los recursos se desasignan automáticamente por el recolector de basura de todos modos?

En realidad, los lenguajes 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 para limpiar correctamente los problemas no relacionados con la memoria.

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

Sin embargo...

RAII traslada 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 programación C mueren con dificultad. Cuando está utilizando una biblioteca escrita en C o en un estilo muy C, RAII no se habrá utilizado. A menos que vuelva a escribir todo el front-end de API, eso es exactamente con lo que tiene que trabajar. Entonces la falta de "finalmente" realmente muerde.


13
Exactamente ... RAII parece agradable desde una perspectiva ideal. Pero tengo que trabajar con API C convencionales todo el tiempo (como las funciones de estilo C en Win32 API ...). Es muy común adquirir un recurso que devuelve algún tipo de HANDLE, que luego requiere alguna función como CloseHandle (HANDLE) para limpiar. Usar try ... finalmente es una buena manera de lidiar con posibles excepciones. (Afortunadamente, parece que shared_ptr con eliminadores personalizados y lambdas C ++ 11 deberían proporcionar un alivio basado en RAII que no requiera escribir clases completas para envolver algunas API que solo uso en un lugar).
James Johnston

77
@JamesJohnston, es muy fácil escribir una clase de contenedor que contenga cualquier tipo de asa y proporcione la mecánica RAII. ATL proporciona un montón de ellos, por ejemplo. Parece que consideras que esto es demasiado problema, pero no estoy de acuerdo, son muy pequeños y fáciles de escribir.
Mark Ransom

55
Simple sí, pequeño no. El tamaño depende de la complejidad de la biblioteca con la que está trabajando.
Philip Couling

1
@MarkRansom: ¿Existe algún mecanismo a través del cual RAII pueda hacer algo inteligente si se produce una excepción durante la limpieza mientras hay otra excepción pendiente? En sistemas con try / finally, es posible, aunque incómodo, organizar las cosas para que la excepción pendiente y la excepción que ocurrió durante la limpieza se almacenen en una nueva CleanupFailedException. ¿Hay alguna forma plausible de lograr ese resultado utilizando RAII?
supercat

3
@couling: hay muchos casos en los que un programa llamará a un SomeObject.DoSomething()método y querrá saber si (1) tuvo éxito, (2) falló sin efectos secundarios , (3) falló con efectos secundarios que la persona que llama está preparada para hacer frente , o (4) fallaron con los efectos secundarios que la persona que llama no puede hacer frente. Solo la persona que llama sabrá qué situaciones puede y no puede hacer frente; lo que necesita la persona que llama es una forma de saber cuál es la situación. Es una pena que no haya un mecanismo estándar para proporcionar la información más importante sobre una excepción.
supercat

9

Otra emulación de bloque "finalmente" usando las funciones lambda de C ++ 11

template <typename TCode, typename TFinallyCode>
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 compilador 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 envolver este modismo en macros "intente - finalmente":

// Please never throw exception below. It is needed to avoid a compilation 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 idioma "finalmente" y preferiría usar la función pura "with_finally" aunque la sintaxis sea más voluminosa en ese caso.

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

PD

Si finalmente necesita un bloque en su código, los guardias de alcance o las macros ON_FINALLY / ON_EXCEPTION probablemente se adaptarán mejor a sus necesidades.

Aquí hay un breve ejemplo de uso ON_FINALLY / ON_EXCEPTION:

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    ...

1
La primera es para mí la más legible de todas las opciones presentadas en esta página. +1
Nikos

7

Perdón por desenterrar un hilo tan antiguo, pero hay un error importante en el siguiente razonamiento:

RAII traslada la responsabilidad de la seguridad de excepción del usuario del objeto al diseñador (e implementador) del objeto. 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 usar finalmente, debe obtener una seguridad de excepción correcta cada vez que use un objeto.

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

void DoStuff(vector<string> input)
{
  list<Foo*> 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 alcance, pero eso no limpiaría los objetos temporales que ha creado.

En cambio, tienes que ir por la ruta fea:

void DoStuff(vector<string> input)
{
  list<Foo*> 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é incluso los idiomas administrados proporcionan un bloqueo final a pesar de que los recursos se desasignan automáticamente por el recolector de basura de todos modos?

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


17
Los lenguajes administrados necesitan finalmente bloques precisamente porque solo se administra automáticamente un tipo de recurso: la memoria. RAII significa que todos los recursos se pueden manejar de la misma manera, por lo que no es necesario finalmente. Si realmente utilizó RAII en su ejemplo (mediante el uso de punteros inteligentes en su lista en lugar de los simples), el código sería más simple que su ejemplo "finalmente". E incluso más simple si no verifica el valor de retorno de new: verificarlo es prácticamente inútil.
Myto

77
newno devuelve NULL, lanza una excepción en su lugar
Hasturkun

55
Planteas una pregunta importante, pero tiene 2 respuestas posibles. Una es la que proporciona Myto: utilice punteros inteligentes para todas las asignaciones dinámicas. El otro es usar contenedores estándar, que siempre destruyen su contenido tras la destrucción. De cualquier manera, cada objeto asignado es en última instancia propiedad de un objeto asignado estáticamente que lo libera automáticamente tras la destrucción. Es una verdadera lástima que estas mejores soluciones sean difíciles de descubrir para los programadores debido a la alta visibilidad de punteros y matrices simples.
j_random_hacker

44
C ++ 11 mejora esto e incluye std::shared_ptry std::unique_ptrdirectamente en stdlib.
u0b34a0f6ae

16
La razón por la que su ejemplo tiene un aspecto tan horrible no es porque RAII sea defectuoso, sino porque no lo usó. Los punteros sin procesar no son RAII.
Ben Voigt

6

FWIW, Microsoft Visual C ++ admite probar, finalmente, e históricamente se ha utilizado en aplicaciones MFC como un método para detectar excepciones graves que de lo contrario provocarían un bloqueo. 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();
    }
}

Lo he usado 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.


44
tenga en cuenta que no se trata realmente de excepciones de C ++, sino de SEH. Puede usar ambos en el código MS C ++. SEH es un controlador de excepciones del sistema operativo que es la forma en que VB, .NET implementan excepciones.
gbjbaanb

y puede usar SetUnhandledExceptionHandler para crear un controlador de excepción "global" no detectado, para excepciones SEH.
gbjbaanb

3
SEH es horrible y también impide que los destructores de C ++ de ser llamado
paulm

6

Como se señaló en las otras respuestas, C ++ puede admitir una finallyfuncionalidad 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 Pautas Básicas de C ++ , un conjunto de mejores prácticas para usar C ++ editado por Bjarne Stoustrup y Herb Sutter. Una implementación definally es parte de la Biblioteca de soporte de guías (GSL). A lo largo de las Pautas, finallyse recomienda el uso de cuando se trata con interfaces de estilo antiguo, y también tiene una pauta propia, titulada Usar un objeto final_action para expresar la limpieza si no hay disponible un manejador de recursos adecuado .

Por lo tanto, no solo es compatible con C ++ finally, en realidad se recomienda usarlo en muchos casos de uso comunes.

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

#include <gsl/gsl_util.h>

void example()
{
    int handle = get_some_resource();
    auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });

    // 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 son muy similares a los de la respuesta de Paolo.Bolzoni . Una diferencia es que el objeto creado por gsl::finally()carece de la disable()llamada. Si necesita esa funcionalidad (por ejemplo, para devolver el recurso una vez que está ensamblado y no se producirán excepciones), es posible que prefiera la implementación de Paolo. De lo contrario, usar GSL es lo más parecido a usar funciones estandarizadas que obtendrá.


3

En realidad no, pero puedes emularlos hasta cierto punto, 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 finalmente 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 Java finalmente. Además, no puede usar returndentro de los bloques try & catch.


3
Me alegro de que hayas mencionado que finalmente podría lanzar el bloque; es lo que la mayoría de las respuestas "use RAII" parecen ignorar. Para evitar tener que escribir el bloque finalmente dos veces, puede hacer algo comostd::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
sethobrien

1
¡Eso es todo lo que quería saber! ¿Por qué ninguna de las otras respuestas explicó que una captura (...) + tiro vacío; funciona casi como un bloque finalmente? A veces solo lo necesitas.
VinGarcia

La solución que proporcioné en mi respuesta ( stackoverflow.com/a/38701485/566849 ) debería permitir lanzar excepciones desde el interior del finallybloque.
Fabio A.

3

Se me ocurrió una finallymacro que se puede usar casi como ¹ la finallypalabra clave en Java; utiliza std::exception_ptry amigos, funciones lambda y std::promise, por lo tanto, requiere C++11o superior; también hace uso de la expresión de declaración compuesta extensión de GCC de , que también es compatible con clang.

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

Primero, definamos una clase auxiliar.

#include <future>

template <typename Fun>
class FinallyHelper {
    template <typename T> struct TypeWrapper {};
    using Return = typename std::result_of<Fun()>::type;

public:    
    FinallyHelper(Fun body) {
        try {
            execute(TypeWrapper<Return>(), body);
        }
        catch(...) {
            m_promise.set_exception(std::current_exception());
        }
    }

    Return get() {
        return m_promise.get_future().get();
    }

private:
    template <typename T>
    void execute(T, Fun body) {
        m_promise.set_value(body());
    }

    void execute(TypeWrapper<void>, Fun body) {
        body();
    }

    std::promise<Return> m_promise;
};

template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
    return FinallyHelper<Fun>(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::promisehace que sea muy fácil de implementar, pero probablemente también introduce un poco de sobrecarga innecesaria que podría evitarse reimplementando solo las funcionalidades necesarias std::promise.


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

  1. que no es posible romper con un bucle exterior con la breakdeclaración del interior de la trye catch()'s bloques, ya que viven dentro de una función lambda;
  2. debe haber al menos un catch()bloque después de try: es un requisito de C ++;
  3. Si la función tiene un valor de retorno distinto de vacío pero no hay retorno dentro de los bloques tryy catch()'s, la compilación fallará porque la finallymacro se expandirá al código que querrá devolver a void. Esto podría ser, err, un vacío al tener una finally_noreturnespecie de macro.

En general, no sé si alguna vez usaría estas cosas yo mismo, pero fue divertido jugar con ellas. :)


Sí, fue solo un truco rápido, pero si el programador sabe lo que están haciendo, podría ser útil de todos modos.
Fabio A.

@ MarkLakata, actualicé la publicación con una mejor implementación que admite lanzar excepciones y devoluciones.
Fabio A.

Se ve bien. Puede deshacerse de Caveat 2 simplemente colocando un catch(xxx) {}bloque imposible al comienzo de la finallymacro, donde xxx es un tipo falso solo con el propósito de tener al menos un bloque de captura.
Mark Lakata

@ MarkLakata, también pensé en eso, pero eso haría que sea imposible de usar catch(...), ¿no?
Fabio A.

No lo creo. Simplemente cree un tipo oscuro xxxen un espacio de nombres privado que nunca se utilizará.
Mark Lakata

2

Tengo un caso de uso en el que 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 consumo / productor, donde un centinelanullptr se envía al final de la ejecución para cerrar todos los hilos.

Si C ++ lo admite, desearía 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 su declaración final al comienzo del ciclo, ya 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 downstreamestá conectada a otro hilo, por lo que no puede poner el centinela push(nullptr)en el destructor downstreamporque no puede ser destruido en este punto ... necesita mantenerse con vida hasta que el otro hilo reciba el nullptr.

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

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> 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);
        }
    }

Hola, creo que mi respuesta anterior ( stackoverflow.com/a/38701485/566849 ) satisface completamente sus requisitos.
Fabio A.

1

Como muchas personas han declarado, la solución es usar las funciones de C ++ 11 para evitar finalmente los bloqueos. Una de las características es unique_ptr.

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

#include <vector>
#include <memory>
#include <list>
using namespace std;

class Foo
{
 ...
};

void DoStuff(vector<string> input)
{
    list<unique_ptr<Foo> > myList;

    for (int i = 0; i < input.size(); ++i)
    {
      myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
    }

    DoSomeStuff(myList);
}

Un poco más de introducción al uso de unique_ptr con contenedores de biblioteca de C ++ estándar es aquí


0

Me gustaría ofrecer una alternativa.

Si finalmente desea que se llame siempre al bloque, simplemente colóquelo después del último bloque catch (que probablemente debería ser catch( ... )para atrapar una 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 cuando se produce una excepción, puede usar la variable local booleana: antes de ejecutarla, configúrela como falsa y coloque la asignación verdadera al final del bloque try, luego, después de la captura del bloque, compruebe 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();
}

Esto no funciona, porque el objetivo de un bloque finalmente es realizar la limpieza incluso cuando el código debe permitir una excepción para abandonar el bloque de código. Considere: `intente {// cosas que posiblemente arrojen" B "} catch (A & a) {} finalmente {// si C ++ lo tuviera ... // cosas que deben suceder, incluso si se arroja" B ". } // no se ejecutará si se lanza "B". `En mi humilde opinión, el punto de excepciones es reducir el código de manejo de errores, por lo que los bloques de captura, donde sea que se produzca un lanzamiento, es contraproducente. Esta es la razón por la cual RAII ayuda: si se aplica generosamente, las excepciones son más importantes en las capas superior e inferior.
burlyearly

1
@burlyearly aunque tu opinión no es sagrada, entiendo el punto, pero en C ++ no existe, así que debes considerar esto como una capa superior que emula este comportamiento.
jave.web

DOWNVOTE = POR FAVOR COMENTARIO :)
jave.web

0

También creo que RIIA no es un reemplazo totalmente útil para el manejo de excepciones y tener un finalmente. Por cierto, también creo que RIIA es un mal nombre en todas partes. Llamo a este tipo de clases 'conserjes' y los uso MUCHO. El 95% de las veces no están inicializando ni adquiriendo recursos, están aplicando algún cambio de forma selectiva, o toman algo ya configurado y se aseguran de que se destruya. Siendo este el patrón oficial de Internet obsesionado, me abusan incluso por sugerir que mi nombre podría ser mejor.

Simplemente no creo que sea razonable exigir que cada configuración complicada de alguna lista ad hoc de cosas tenga que tener una clase escrita para contenerla con el fin de evitar complicaciones al limpiar todo de nuevo ante la necesidad de atrapar múltiples tipos de excepción si algo sale mal en el proceso. Esto llevaría a muchas clases ad hoc que de otro modo no serían necesarias.

Sí, está bien para las clases que están diseñadas para administrar un recurso en particular, o genéricas que están diseñadas para manejar un conjunto de recursos similares. Pero, incluso si todas las cosas involucradas tienen tales envoltorios, la coordinación de la limpieza puede no ser una simple invocación de destructores en orden inverso.

Creo que tiene mucho sentido que C ++ tenga un finalmente. Quiero decir, caramba, se han pegado tantas partes y bobs en las últimas décadas que parece extraño que la gente de repente se vuelva conservadora sobre algo que finalmente podría ser bastante útil y probablemente nada tan complicado como otras cosas que han sido agregado (aunque eso es solo una suposición de mi parte).


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

35
Lindo idioma, pero no es lo mismo. regresar en el bloque try o catch no pasará por su código 'finalmente:'.
Edward KMETT el

10
Vale la pena mantener esta respuesta incorrecta (con una calificación de 0), ya que Edward Kmett saca una distinción muy importante.
Mark Lakata

12
Defecto aún mayor (IMO): este código come todas las excepciones, lo finallyque no hace.
Ben Voigt
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.