¿Es necesaria la parte 'finalmente' de una construcción 'intentar ... atrapar ... finalmente'?

25

Algunos lenguajes (como C ++ y versiones anteriores de PHP) no admiten la finallyparte de una try ... catch ... finallyconstrucción. ¿Es finallynecesario alguna vez? Debido a que el código siempre se ejecuta, ¿por qué no debería / no debería colocar ese código después de un try ... catchbloque sin una finallycláusula? ¿Por qué usar uno? (Estoy buscando una razón / motivación para usar / no usar finally, no una razón para eliminar 'atrapar' o por qué es legal hacerlo).

Agi Hammerthief
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
maple_shaft

Respuestas:

36

Además de lo que otros han dicho, también es posible que se arroje una excepción dentro de la cláusula catch. Considera esto:

try { 
    throw new SomeException();
} catch {
    DoSomethingWhichUnexpectedlyThrows();
}
Cleanup();

En este ejemplo, la Cleanup()función nunca se ejecuta, porque se genera una excepción en la cláusula catch y la siguiente captura más alta en la pila de llamadas lo captará. Usar un bloque finalmente elimina este riesgo y hace que el código sea más limpio para arrancar.

Erik
fuente
44
Gracias por una respuesta concisa y directa que no se desvíe de la teoría y del territorio 'lenguaje X es mejor que Y'.
Agi Hammerthief
56

Como otros han mencionado, no hay garantía de que el código después de una trydeclaración se ejecute a menos que detecte todas las excepciones posibles. Dicho esto, esto:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   handleError();
} finally {
   cleanUp();
}

puede reescribirse 1 como:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   try {
       handleError();
   } catch (Throwable e2) {
       cleanUp();
       throw e2;
   }
} catch (Throwable e) {
   cleanUp();
   throw e;
}
cleanUp();

Pero esto último requiere que capture todas las excepciones no controladas, duplique el código de limpieza y recuerde volver a lanzar. Entonces finallyno es necesario , pero es útil .

C ++ no tiene finallyporque Bjarne Stroustrup cree que RAII es mejor , o al menos es suficiente para la mayoría de los casos:

¿Por qué C ++ no proporciona una construcción "finalmente"?

Debido a que C ++ admite una alternativa que casi siempre es mejor: la técnica de "adquisición de recursos es inicialización" (TC ++ PL3 sección 14.4). La idea básica es representar un recurso por un objeto local, de modo que el destructor del objeto local libere el recurso. De esa manera, el programador no puede olvidar liberar el recurso.


1 El código específico para capturar todas las excepciones y volver a lanzar sin perder la información de seguimiento de la pila varía según el idioma. He utilizado Java, donde se captura el seguimiento de la pila cuando se crea la excepción. En C # simplemente usarías throw;.

Doval
fuente
8
También tienes que atrapar las excepciones handleError()en el segundo caso, ¿no?
Juri Robl
1
También puede estar arrojando un error. Reformularía eso catch (Throwable t) {}, con el intento ... atrapar bloque alrededor del bloque inicial completo (para atrapar handleErrortambién los
lanzamientos
1
De hecho, agregaría el try-catch adicional que omitió al llamar, lo handleErro();que lo hará aún mejor argumento de por qué finalmente los bloques son útiles (aunque esa no era la pregunta original).
Alex
1
Esta respuesta realmente no aborda la pregunta de por qué C ++ no tiene finally, que es mucho más matizado.
DeadMG
1
@AgiHammerthief El anidado tryestá dentro catchde la excepción específica . En segundo lugar, es posible que no sepa si puede manejar el error con éxito hasta que haya examinado la excepción, o que la causa de la excepción también le impida manejar el error (al menos a ese nivel). Eso es bastante común al hacer E / S. La repetición está ahí porque la única forma de garantizar cleanUpejecuciones es atrapar todo , pero el código original permitiría que las excepciones que se originan en el catch (SpecificException e)bloque se propaguen hacia arriba.
Doval
22

finally Los bloques generalmente se usan para eliminar recursos que pueden ayudar con la legibilidad cuando se usan múltiples declaraciones de retorno:

int DoSomething() {
    try {
        open_connection();
        return get_result();
    }
    catch {
        return 2;
    }
    finally {
        close_connection();
    }
}

vs

int DoSomething() {
    int result;
    try {
        open_connection();
        result = get_result();
    }
    catch {
        result = 2;
    }
    close_connection();
    return result;
}
AlexFoxGill
fuente
2
Creo que esta es la mejor respuesta. Usar un finalmente como reemplazo de una excepción genérica parece una mierda. El caso de uso correcto es limpiar recursos u operaciones análogas.
Kik
3
Quizás aún más común es regresar dentro del bloque try, en lugar de dentro del bloque catch.
Michael Anderson
En mi opinión, el código no explica adecuadamente el uso de finally. (Usaría el código como en el segundo bloque ya que se desaconsejan las declaraciones de retorno múltiples donde trabajo.)
Agi Hammerthief
15

Como aparentemente ya has supuesto, sí, C ++ proporciona las mismas capacidades sin ese mecanismo. Como tal, estrictamente hablando, el mecanismo try/ finallyno es realmente necesario.

Dicho esto, prescindir de él impone algunos requisitos en la forma en que se diseña el resto del lenguaje. En C ++, el mismo conjunto de acciones está incorporado en el destructor de una clase. Esto funciona principalmente (¿exclusivamente?) Porque la invocación del destructor en C ++ es determinista. Esto, a su vez, conduce a algunas reglas bastante complejas sobre la vida útil de los objetos, algunas de las cuales son decididamente no intuitivas.

La mayoría de los otros idiomas proporcionan alguna forma de recolección de basura. Si bien hay cosas sobre la recolección de basura que son controvertidas (por ejemplo, su eficiencia en relación con otros métodos de administración de memoria), una cosa generalmente no lo es: el momento exacto en que el recolector de basura "limpia" un objeto no está directamente vinculado al alcance del objeto. Esto evita su uso cuando la limpieza debe ser determinista, ya sea simplemente para un funcionamiento correcto, o cuando se trata de recursos tan valiosos que la limpieza no se retrasa arbitrariamente. try/ finallyproporciona una forma para que dichos lenguajes aborden aquellas situaciones que requieren una limpieza determinista.

Creo que aquellos que afirman que la sintaxis de C ++ para esta capacidad es "menos amigable" que la de Java, están perdiendo el punto. Peor aún, se están perdiendo un punto mucho más crucial sobre la división de responsabilidad que va mucho más allá de la sintaxis y tiene mucho más que ver con la forma en que se diseña el código.

En C ++, esta limpieza determinista ocurre en el destructor del objeto. Eso significa que el objeto puede estar (y normalmente debería estar) diseñado para limpiarse después de sí mismo. Esto va a la esencia del diseño orientado a objetos: una clase debe diseñarse para proporcionar una abstracción y hacer cumplir sus propias invariantes. En C ++, uno hace precisamente eso, y uno de los invariantes que proporciona es que cuando se destruye el objeto, los recursos controlados por ese objeto (todos ellos, no solo la memoria) se destruirán correctamente.

Java (y similares) son algo diferentes. Si bien admiten (más o menos) un tipo finalizeque teóricamente podría proporcionar capacidades similares, el soporte es tan débil que es básicamente inutilizable (y de hecho, esencialmente nunca se usa).

Como resultado, en lugar de que la clase misma pueda realizar la limpieza requerida, el cliente de la clase debe tomar medidas para hacerlo. Si hacemos una comparación suficientemente miope, a primera vista puede parecer que esta diferencia es bastante menor y Java es bastante competitivo con C ++ a este respecto. Terminamos con algo como esto. En C ++, la clase se ve así:

class Foo {
    // ...
public:
    void do_whatever() { if (xyz) throw something; }
    ~Foo() { /* handle cleanup */ }
};

... y el código del cliente se ve así:

void f() { 
    Foo f;
    f.do_whatever();
    // possibly more code that might throw here
}

En Java intercambiamos un poco más de código donde el objeto se usa por un poco menos en la clase. Inicialmente, esto parece una compensación bastante pareja. Sin embargo, en realidad está lejos de serlo, porque en el código más típico solo definimos la clase en un lugar, pero la usamos en muchos lugares. El enfoque de C ++ significa que solo escribimos ese código para manejar la limpieza en un solo lugar. El enfoque de Java significa que tenemos que escribir ese código para manejar la limpieza muchas veces, en muchos lugares, en cada lugar donde usamos un objeto de esa clase.

En resumen, el enfoque de Java básicamente garantiza que muchas abstracciones que intentamos proporcionar son "permeables": cualquier clase que requiera una limpieza determinista obliga al cliente de la clase a conocer los detalles de qué limpiar y cómo hacerlo. , en lugar de que esos detalles estén ocultos en la clase misma.

Aunque lo he llamado "el enfoque de Java" anteriormente, try/ finallyy mecanismos similares bajo otros nombres no están completamente restringidos a Java. Para un ejemplo destacado, la mayoría (¿todos?) De los lenguajes .NET (por ejemplo, C #) proporcionan lo mismo.

Las iteraciones recientes de Java y C # también proporcionan un punto intermedio entre Java "clásico" y C ++ a este respecto. En C #, un objeto que quiere automatizar su limpieza puede implementar la IDisposableinterfaz, que proporciona un Disposemétodo (al menos vagamente) similar a un destructor de C ++. Si bien esto se puede usar a través de un try/ finallylike en Java, C # automatiza la tarea un poco más con una usingdeclaración que le permite definir los recursos que se crearán a medida que se ingresa un alcance y se destruyen cuando se sale del alcance. Aunque todavía está muy por debajo del nivel de automatización y certeza proporcionado por C ++, esta sigue siendo una mejora sustancial sobre Java. En particular, el diseñador de la clase puede centralizar los detalles de cómodisponer de la clase en su implementación de IDisposable. Todo lo que queda para el programador del cliente es la menor carga de escribir una usingdeclaración para garantizar que la IDisposableinterfaz se utilizará cuando debería. En Java 7 y versiones posteriores, los nombres se han cambiado para proteger al culpable, pero la idea básica es básicamente idéntica.

Jerry Coffin
fuente
1
Respuesta perfecta. Los destructores son LA característica imprescindible en C ++.
Thomas Eding
13

No puedo creer que nadie más haya planteado esto (sin juego de palabras): ¡no necesita una cláusula catch !

Esto es perfectamente razonable:

try 
{
   AcquireManyResources(); 
   DoSomethingThatMightFail(); 
}
finally 
{
   CleanUpThoseResources(); 
}

No hay ninguna cláusula catch a la vista, porque este método no puede hacer nada útil con esas excepciones; se dejan propagar de nuevo la pila de llamadas a un controlador que pueda . Capturar y volver a lanzar excepciones en cada método es una mala idea, especialmente si solo estás volviendo a lanzar la misma excepción. Va completamente en contra de cómo se supone que funciona el Manejo de excepciones estructuradas (y está muy cerca de devolver un "código de error" de cada método, solo en la "forma" de una Excepción).

Lo que este método no tiene que ver, sin embargo, para limpiar después de sí mismo, de modo que el "mundo exterior" no necesita saber nada sobre el desorden que se puso en sí. La última cláusula hace exactamente eso: no importa cómo se comporten los métodos llamados, la cláusula final se ejecutará "al salir" del método (y lo mismo es cierto para cada cláusula final entre el punto en el que se lanza la Excepción y la eventual cláusula catch que lo maneja); cada uno se ejecuta mientras la pila de llamadas "se desenrolla".

Phill W.
fuente
9

¿Qué pasaría si se lanzara una excepción que no esperaba? El intento saldría en el medio y no se ejecuta ninguna cláusula catch.

El último bloque es ayudar con eso y garantizar que, sin importar la excepción, la limpieza se realice.

monstruo de trinquete
fuente
44
Esa no es razón suficiente para una finally, ya que puede evitar excepciones "inesperadas" con catch(Object)o catch(...)generalidades.
MSalters
1
Lo que suena como una solución. Conceptualmente finalmente es más limpio. Aunque debo confesar que rara vez lo uso.
rapid_now
7

Algunos lenguajes ofrecen tanto constructores como destructores para sus objetos (por ejemplo, C ++, creo). Con estos idiomas puede hacer la mayoría (posiblemente todo) de lo que generalmente se hace finallyen un destructor. Como tal, en esos idiomas, una finallycláusula puede ser superflua.

En un lenguaje sin destructores (por ejemplo, Java) es difícil (tal vez incluso imposible) lograr una limpieza correcta sin la finallycláusula. NB: en Java hay un finalisemétodo, pero no hay garantía de que alguna vez se llame.

OldCurmudgeon
fuente
Puede ser útil tener en cuenta que los destructores ayudan a limpiar los recursos cuando la destrucción es determinista . Si no sabemos cuándo se destruirá el objeto y / o se recolectará basura, entonces los destructores no son lo suficientemente seguros.
Morwenn
@ Morwenn - Buen punto. Lo insinué con mi referencia a Java, finalisepero preferiría no entrar en los argumentos políticos sobre destructores / finalistas en este momento.
OldCurmudgeon
En C ++ la destrucción es determinista. Cuando el alcance que contiene un objeto automático sale (por ejemplo, se saca de la pila), se llama a su destructor. (C ++ le permite asignar objetos en la pila, no solo el montón).
Rob K
@RobK: y esta es la funcionalidad exacta de un finalisepero con un sabor extensible y un mecanismo tipo OOP, muy expresivo y comparable al finalisemecanismo de otros idiomas.
OldCurmudgeon
1

Intentar finalmente y tratar de atrapar son dos cosas diferentes que solo comparten la palabra clave "probar". Personalmente me hubiera gustado ver eso diferente. La razón por la que los ven juntos es porque las excepciones producen un "salto".

E intentar finalmente está diseñado para ejecutar código incluso si el flujo de programación salta. Ya sea por una excepción o por cualquier otra razón. Es una buena forma de adquirir un recurso y asegurarse de que se limpie después sin tener que preocuparse por los saltos.

Pieter B
fuente
3
En .NET se implementan utilizando mecanismos separados; en Java, sin embargo, la única construcción reconocida por la JVM es semánticamente equivalente a "en error goto", un patrón que admite directamente try catchpero no try finally; el código que usa este último se convierte en código usando solo el primero, copiando el contenido del finallybloque en todos los puntos del código donde podría necesitar ejecutarse.
supercat
@supercat nice, gracias por la información adicional sobre Java.
Pieter B
1

Como esta pregunta no especifica C ++ como lenguaje, consideraré una combinación de C ++ y Java, ya que adoptan un enfoque diferente para la destrucción de objetos, que se sugiere como una de las alternativas.

Motivos por los que puede usar un bloque finalmente, en lugar de un código después del bloque try-catch

  • regresas temprano desde el bloque try: considera esto

    Database db = null;
    try {
     db = open_database();
     if(db.isSomething()) {
       return 7;
     }
     return db.someThingElse();
    } finally {
      if(db!=null)
        db.close();
    }
    

    comparado con:

    Database db = null;
    int returnValue = 0;
    try {
     db = open_database();
     if(db.isSomething()) {
       returnValue = 7;
     } else {
       returnValue = db.someThingElse();
     }
    } catch(Exception e) {
      if(db!=null)
        db.close();
    }
    return returnValue;
    
  • regresas temprano de los bloques de captura: compara

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      return 7;
    } catch (DBIsADonkeyException e ) {
      return 11;
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null) 
        db.close();
      return 7;
    } catch (DBIsADonkeyException e ) {
      if(db!=null)
        db.close();
      return 11;
    }           
    db.close();
    
  • Revocas excepciones. Comparar:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      throw convertToRuntimeException(e,"DB was wonkey");
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null)
        db.close();
      throw convertToRuntimeException(e,"DB was wonkey");
    } 
    if(db!=null)
      db.close();
    

Estos ejemplos no hacen que parezca tan malo, pero a menudo tiene varios de estos casos interactuando y más de un tipo de excepción / recurso en juego. finallypuede ayudar a evitar que su código se convierta en una pesadilla de mantenimiento enredada.

Ahora en C ++ estos pueden manejarse con objetos basados ​​en el alcance. Pero en mi opinión, hay dos desventajas para este enfoque 1. La sintaxis es menos amigable. 2. El orden de construcción al reverso del orden de destrucción puede aclarar las cosas.

En Java, no puedes conectar el método de finalización para hacer tu limpieza, ya que no sabes cuándo sucederá (bueno, pero ese es un camino lleno de divertidas condiciones de carrera: JVM tiene mucho margen para decidir cuándo destruye cosas, a menudo no es cuando lo espera, ya sea antes o después de lo que podría esperar, y eso puede cambiar a medida que el compilador de puntos calientes se activa ... suspiro ...)

Michael Anderson
fuente
1

Todo lo que es lógicamente "necesario" en un lenguaje de programación son las instrucciones:

assignment a = b
subtract a from b
goto label
test a = 0
if true goto label

Cualquier algoritmo se puede implementar utilizando solo las instrucciones anteriores, todas las demás construcciones de lenguaje están ahí para hacer que los programas sean más fáciles de escribir y más comprensibles para otros programadores.

Vea la vieja computadora para el hardware real usando un conjunto de instrucciones tan mínimo.

James Anderson
fuente
1
Su respuesta es ciertamente cierta, pero no codifico en el ensamblaje; Es muy doloroso. Me pregunto por qué usar una función que no veo en los idiomas que lo admiten, no cuál es el conjunto mínimo de instrucciones de un idioma.
Agi Hammerthief
1
El punto es que cualquier lenguaje que implemente solo estas 5 operaciones puede implementar cualquier algoritmo, aunque de manera bastante tortuosa. La mayoría de los vers / operadores en lenguajes de alto nivel no son "necesarios" si el objetivo es simplemente implementar un algoritmo. Si el objetivo es tener un desarrollo rápido de código legible y mantenible, entonces la mayoría son necesarios, pero "legible" y "mantenible" no son medibles y extremadamente subjetivos. Los buenos desarrolladores de lenguaje incorporan muchas características: si no tiene uso para algunas de ellas, entonces no las use.
James Anderson
0

En realidad, la brecha más grande para mí suele estar en los idiomas que admiten finallypero carecen de destructores, porque puede modelar toda la lógica asociada con la "limpieza" (que separaré en dos categorías) a través de destructores a nivel central sin tener que lidiar manualmente con la limpieza lógica en cada función relevante. Cuando veo que C # o el código Java hacen cosas como desbloquear manualmente mutexes y cerrar archivos en finallybloques, eso se siente desactualizado y algo así como el código C cuando todo eso está automatizado en C ++ a través de destructores de manera que libera a los humanos de esa responsabilidad.

Sin embargo, todavía encontraría una leve comodidad si se incluye C ++ finallyy es porque hay dos tipos de limpieza:

  1. Destruir / liberar / desbloquear / cerrar / etc. recursos locales (los destructores son perfectos para esto).
  2. Deshacer / deshacer los efectos secundarios externos (los destructores son adecuados para esto).

El segundo, al menos, no se asigna tan intuitivamente a la idea de la destrucción de recursos, aunque puede hacerlo bien con los protectores de alcance que revierten automáticamente los cambios cuando se destruyen antes de ser cometidos. No finallypodría decirse que proporciona al menos un poco (sólo un poco de pequeñísima) mecanismo más sencillo para el trabajo de los guardias de alcance.

Sin embargo, un mecanismo aún más directo sería un rollbackbloque que nunca antes había visto en ningún idioma. Es una especie de sueño mío si alguna vez diseñé un lenguaje que involucrara el manejo de excepciones. Se parecería a esto:

try
{
    // Cause external side effects. These side effects should
    // be undone if we don't finish successfully.
}
rollback
{
    // Reverse external side effects. This block is *only* executed 
    // if the 'try' block above faced a premature return out 
    // of the function. It is different from 'finally' which 
    // gets executed regardless of whether or not the function 
    // exited prematurely. This block *only* gets executed if we 
    // exited prematurely from  the try block so that we can undo 
    // whatever side effects it failed to finish making. If the try 
    // block succeeded and didn't face a premature exit, then we 
    // don't want this block to execute.
}

Esa sería la forma más sencilla de modelar las reversiones de efectos secundarios, mientras que los destructores son prácticamente el mecanismo perfecto para la limpieza de recursos locales. Ahora solo guarda un par de líneas adicionales de código de la solución de protección de alcance, pero la razón por la que quiero ver un lenguaje con esto es que la reversión de efectos secundarios tiende a ser el aspecto más descuidado (pero más complicado) del manejo de excepciones en lenguajes que giran en torno a la mutabilidad. Creo que esta característica alentaría a los desarrolladores a pensar en el manejo de excepciones de la manera adecuada en términos de deshacer las transacciones siempre que las funciones causen efectos secundarios y no se completen y, como una ventaja adicional, cuando las personas ven lo difícil que puede ser hacer retrocesos correctamente, En primer lugar, podrían favorecer escribir más funciones libres de efectos secundarios.

También hay algunos casos oscuros en los que solo desea hacer cosas diversas sin importar qué al salir de una función, independientemente de cómo salió, como tal vez registrar una marca de tiempo. Podría finallydecirse que es la solución más sencilla y perfecta para el trabajo, ya que tratar de crear una instancia de un objeto solo para usar su destructor con el único propósito de registrar una marca de tiempo simplemente se siente realmente extraño (aunque puede hacerlo bien y de manera conveniente con lambdas )


fuente
-9

Al igual que muchas otras cosas inusuales sobre el lenguaje C ++, la falta de una try/finallyconstrucción es una falla de diseño, incluso si puede llamarlo así en un lenguaje que con frecuencia parece no haber realizado ningún trabajo de diseño real .

RAII (el uso de invocación de destructor determinista basada en el alcance en objetos basados ​​en pila para la limpieza) tiene dos fallas graves. La primera es que requiere el uso de objetos basados ​​en la pila , que son una abominación que viola el Principio de sustitución de Liskov. Hay muchas buenas razones por las cuales ningún otro lenguaje OO antes o después de que C ++ los haya usado, dentro de epsilon; D no cuenta ya que se basa en gran medida en C ++ y no tiene participación de mercado de todos modos, y explicar los problemas que causan está más allá del alcance de esta respuesta.

En segundo lugar, lo que finallypuede hacer es un superconjunto de destrucción de objetos. Gran parte de lo que se hace con RAII en C ++ se describiría en el lenguaje Delphi, que no tiene recolección de basura, con el siguiente patrón:

myObject := MyClass.Create(arguments);
try
   doSomething(myObject);
finally
   myObject.Free();
end;

Este es el patrón RAII hecho explícito; Si fuera a crear una rutina C ++ que contenga solo el equivalente a la primera y tercera líneas anteriores, lo que generaría el compilador terminaría pareciéndose a lo que escribí en su estructura básica. Y debido a que es el único acceso a la try/finallyconstrucción que proporciona C ++, los desarrolladores de C ++ terminan con una visión bastante miope de try/finally: cuando todo lo que tienes es un martillo, todo comienza a verse como un destructor, por así decirlo.

Pero hay otras cosas que un desarrollador experimentado puede hacer con una finallyconstrucción. No se trata de la destrucción determinista, incluso frente a una excepción planteada; se trata de la ejecución de código determinista , incluso ante una excepción que se genera.

Aquí hay otra cosa que puede ver comúnmente en el código de Delphi: un objeto de conjunto de datos con controles de usuario vinculados a él. El conjunto de datos contiene datos de una fuente externa, y los controles reflejan el estado de los datos. Si está a punto de cargar una gran cantidad de datos en su conjunto de datos, querrá deshabilitar temporalmente el enlace de datos para que no haga cosas extrañas en su interfaz de usuario, tratando de actualizarlo una y otra vez con cada nuevo registro que ingrese , así que lo codificaría así:

dataset.DisableControls();
try
   LoadData(dataset);
finally
   dataset.EnableControls();
end;

Claramente, no hay ningún objeto destruido aquí, y no hay necesidad de uno. El código es simple, conciso, explícito y eficiente.

¿Cómo se haría esto en C ++? Bueno, primero tendrías que codificar una clase entera . Probablemente se llamaría DatasetEnablero algo así. Toda su existencia sería como un ayudante RAII. Entonces necesitarías hacer algo como esto:

dataset.DisableControls();
{
   raiiGuard = DatasetEnabler(dataset);
   LoadData(dataset);
}

Sí, esas llaves aparentemente superfluas son necesarias para administrar el alcance adecuado y garantizar que el conjunto de datos se vuelva a habilitar de inmediato y no al final del método. Entonces, lo que termina no requiere menos líneas de código (a menos que use llaves egipcias). Requiere que se cree un objeto superfluo, que tiene sobrecarga. (¿No se supone que el código C ++ es rápido?) No es explícito, sino que depende de la magia del compilador. El código que se ejecuta no se describe en ninguna parte de este método, sino que reside en una clase completamente diferente, posiblemente en un archivo completamente diferente . En resumen, de ninguna manera es una mejor solución que poder escribir el try/finallybloque usted mismo.

Este tipo de problema es lo suficientemente común en el diseño del lenguaje que tiene un nombre: inversión de abstracción. Ocurre cuando una construcción de alto nivel se construye encima de una construcción de bajo nivel, y luego la construcción de bajo nivel no se admite directamente en el lenguaje, lo que requiere que aquellos que desean usarla la vuelvan a implementar en términos de construcción de alto nivel, a menudo con fuertes penalizaciones tanto para la legibilidad como para la eficiencia del código.

Mason Wheeler
fuente
Los comentarios están destinados a aclarar o mejorar una pregunta y respuesta. Si desea tener una discusión sobre esta respuesta, vaya a la sala de chat. Gracias.
maple_shaft