¿C ++ admite bloques ' finalmente '?
¿Cuál es el idioma de RAII ?
¿Cuál es la diferencia entre el lenguaje RAII de C ++ y la declaración 'usando' de C # ?
¿C ++ admite bloques ' finalmente '?
¿Cuál es el idioma de RAII ?
¿Cuál es la diferencia entre el lenguaje RAII de C ++ y la declaración 'usando' de C # ?
Respuestas:
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.
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.
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.
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
// 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.
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"; }
FinalAction
es básicamente lo mismo que el ScopeGuard
idioma popular , solo que con un nombre diferente.
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.
¿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.
CleanupFailedException
. ¿Hay alguna forma plausible de lograr ese resultado utilizando RAII?
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.
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(); });
...
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.
new
no devuelve NULL, lanza una excepción en su lugar
std::shared_ptr
y std::unique_ptr
directamente en stdlib.
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.
Como se señaló en las otras respuestas, C ++ puede admitir una finally
funcionalidad 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, finally
se 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á.
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 return
dentro de los bloques try & catch.
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
bloque.
Se me ocurrió una finally
macro que se puede usar casi como ¹ la finally
palabra clave en Java; utiliza std::exception_ptr
y amigos, funciones lambda y std::promise
, por lo tanto, requiere C++11
o 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::promise
hace 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:
break
declaración del interior de la try
e catch()
's bloques, ya que viven dentro de una función lambda;catch()
bloque después de try
: es un requisito de C ++;try
y catch()'s
, la compilación fallará porque la finally
macro se expandirá al código que querrá devolver a void
. Esto podría ser, err, un vacío al tener una finally_noreturn
especie de macro.En general, no sé si alguna vez usaría estas cosas yo mismo, pero fue divertido jugar con ellas. :)
catch(xxx) {}
bloque imposible al comienzo de la finally
macro, donde xxx es un tipo falso solo con el propósito de tener al menos un bloque de captura.
catch(...)
, ¿no?
xxx
en un espacio de nombres privado que nunca se utilizará.
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 downstream
está conectada a otro hilo, por lo que no puede poner el centinela push(nullptr)
en el destructor downstream
porque 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);
}
}
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í
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();
}
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).
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
que no hace.