web-dev-qa-db-fra.com

C ++ prend-il en charge les blocs "finalement"? (Et de quoi parle-t-on cette 'RAII'?)

C++ prend-il en charge les blocs ' et enfin '?

Qu'est-ce que le idiome RAII ?

Quelle est la différence entre l'idiome RAII de C++ et C # s 'using' statement ?

252
Kevin

Non, C++ ne prend pas en charge les blocs "finalement". La raison en est que C++ prend en charge à la place RAII: "L'initialisation est une acquisition de ressource" - un nom pauvre  pour un concept vraiment utile.

L'idée est que le destructeur d'un objet est responsable de la libération des ressources. Lorsque l'objet a une durée de stockage automatique, le destructeur de l'objet est appelé à la sortie du bloc dans lequel il a été créé, même lorsque ce bloc est quitté en présence d'une exception. Voici l'explication de Bjarne Stroustrup du sujet.

Une utilisation courante de RAII est de verrouiller 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 simplifie également l'utilisation d'objets en tant que membres d'autres classes. Lorsque la classe propriétaire 'est détruite, la ressource gérée par la classe RAII est libérée car le destructeur de la classe gérée RAII est appelé en conséquence. Cela signifie que lorsque vous utilisez RAII pour tous les membres d'une classe qui gère des ressources, vous pouvez vous en sortir en utilisant un destructeur très simple, peut-être même par défaut, pour la classe propriétaire, car elle n'a pas besoin de gérer manuellement les durées de vie de ses ressources de membre. . (Merci à Mike B pour l'avoir signalé.)

Pour ceux qui connaissent bien C # ou VB.NET, vous reconnaîtrez peut-être que RAII est similaire à . NET destruction destructrice à l’aide des instructions IDisposable et 'using' . En effet, les deux méthodes sont très similaires. La principale différence est que RAII libérera de manière déterministe tout type de ressource, y compris la mémoire. Lors de la mise en œuvre d'IDisposable dans .NET (même en C++/CLI en langage .NET), les ressources seront libérées de manière déterministe, à l'exception de la mémoire. Dans .NET, la mémoire n'est pas libérée de manière déterministe. la mémoire n'est libérée que pendant les cycles de récupération de place.

† Certaines personnes pensent que "détruire, c'est libérer des ressources" est un nom plus exact pour l'idiome RAII.

258
Kevin

En C++ enfin, c'est NE PAS nécessaire en raison de RAII.

RAII déplace la responsabilité de la sécurité des exceptions de l'utilisateur de l'objet au concepteur (et à son implémenteur) de l'objet. Je dirais que c’est le bon endroit car il suffit alors d’obtenir la sécurité des exceptions correcte une fois (dans la conception/la mise en œuvre). En utilisant enfin, vous devez corriger correctement la sécurité des exceptions chaque fois que vous utilisez un objet.

Aussi, le code IMO semble plus net (voir ci-dessous).

Exemple:

Un objet de base de données. Pour vous assurer que la connexion à la base de données est utilisée, vous devez l'ouvrir et la fermer. En utilisant RAII, cela peut être fait dans le constructeur/destructeur.

C++ comme 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.

L'utilisation de RAII facilite l'utilisation d'un objet de base de données. L'objet DB se fermera correctement par l'utilisation d'un destructeur, peu importe comment nous essayons d'en abuser.

Java comme enfin

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.
        }
    }
}

Lors de l'utilisation finale, l'utilisation correcte de l'objet est déléguée à l'utilisateur de l'objet. c'est à dire. Il est de la responsabilité de l’utilisateur de l’objet de fermer correctement et explicitement la connexion à la base de données. Vous pouvez maintenant dire que cela peut être fait dans le finaliseur, mais les ressources peuvent avoir une disponibilité limitée ou d'autres contraintes et vous souhaitez donc généralement contrôler la publication de l'objet et ne pas vous fier au comportement non déterministe du garbage collector.

Aussi, ceci est un exemple simple.
Lorsque vous avez plusieurs ressources à libérer, le code peut devenir compliqué.

Une analyse plus détaillée peut être trouvée ici: http://accu.org/index.php/journals/236

74
Martin York

RAII est généralement préférable, mais vous pouvez facilement avoir la sémantique enfin en C++. Utiliser une petite quantité de code.

En outre, le C++ Core Guidelines donne finalement.

Voici un lien vers implémentation GSL de Microsoft et un lien vers implémentation de Martin Moene

Bjarne Stroustrup a répété à maintes reprises que tout ce qui se trouvait dans le GSL signifiait être intégré à la norme. Donc, ce devrait être une manière pérenne d'utiliser enfin .

Vous pouvez facilement mettre en œuvre vous-même si vous voulez, continuez à lire.

En C++ 11, RAII et lambdas permettent de faire un général enfin:

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

exemple d'utilisation:

#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 sortie sera:

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

Personnellement, j'ai utilisé ces quelques fois pour m'assurer de fermer le descripteur de fichier POSIX dans un programme C++.

Avoir une classe réelle qui gère les ressources et évite ainsi tout type de fuite est généralement préférable, mais ceci enfin est utile dans les cas où créer une classe ressemble à une surdose.

De plus, je l’aime mieux que d’autres langues enfin , car si vous l’utilisez naturellement, vous écrivez le code de fermeture près du code d’ouverture (dans mon exemple, le new et delete ) et la destruction suit la construction dans LIFO comme habituel en C++. Le seul inconvénient est que vous obtenez une variable automatique que vous n'utilisez pas vraiment et que la syntaxe lambda la rend un peu bruyante (dans mon exemple, à la quatrième ligne, seul le mot enfin et le {} - bloc à droite sont significatifs, le reste est essentiellement du bruit).

Un autre exemple:

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

Le membre disable est utile si le finally doit être appelé uniquement dans cas d'échec. Par exemple, vous devez copier un objet dans trois conteneurs différents, vous pouvez configurer le enfin pour annuler chaque copie et la désactiver une fois toutes les copies terminées. Ce faisant, si la destruction ne peut pas lancer, vous garantissez la garantie forte.

désactiver exemple:

//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 vous ne pouvez pas utiliser C++ 11, vous pouvez toujours avoir enfin , mais le code devient un peu plus long. Définissez simplement une structure avec uniquement un constructeur et un destructeur, le constructeur prend des références à tout ce dont vous avez besoin et le destructeur effectue les actions dont vous avez besoin. C'est essentiellement ce que fait le lambda, fait manuellement.

#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"; }
55
Paolo.Bolzoni

En plus de faciliter le nettoyage avec les objets en pile, RAII est également utile, car le même nettoyage "automatique" a lieu lorsque l'objet est membre d'une autre classe. Lorsque la classe propriétaire est détruite, la ressource gérée par la classe RAII est nettoyée, car le nom de cette classe est appelé en conséquence.

Cela signifie que lorsque vous atteignez RAII, nirvana et que tous les membres d’une classe utilisent RAII (comme les pointeurs intelligents), vous pouvez vous en tirer avec un dtor très simple (peut-être même par défaut) pour la classe propriétaire, car elle n’a pas besoin de la gérer manuellement. Durée de vie des ressources membres.

30
Michael Burr

pourquoi est-ce que même les langages gérés fournissent un blocage final malgré les ressources désallouées automatiquement par le ramasse-miettes de toute façon?

En fait, les langues basées sur les éboueurs ont besoin "enfin" de plus. Un ramasse-miettes ne détruit pas vos objets à temps, vous ne pouvez donc pas compter sur lui pour nettoyer correctement les problèmes non liés à la mémoire.

Pour ce qui est des données allouées dynamiquement, bon nombre de personnes diraient que vous devriez utiliser des pointeurs intelligents.

Pourtant...

RAII déplace la responsabilité de la sécurité des exceptions de l'utilisateur de l'objet au concepteur

Malheureusement, c'est sa propre chute. Les vieilles habitudes de programmation en C ont la vie dure. Lorsque vous utilisez une bibliothèque écrite en C ou dans un style très C, RAII n'aura pas été utilisé. Sans avoir à réécrire l'intégralité de l'interface frontale de l'API, c'est exactement ce avec quoi vous devez travailler. Alors le manque de "finalement" mord vraiment.

30
Philip Couling

Désolé de creuser un fil aussi ancien, mais le raisonnement suivant présente une erreur majeure:

RAII déplace la responsabilité de la sécurité des exceptions de l'utilisateur de l'objet au concepteur (et à son implémenteur) de l'objet. Je dirais que c’est le bon endroit car il suffit alors d’obtenir la sécurité des exceptions correcte une fois (dans la conception/la mise en œuvre). En utilisant enfin, vous devez corriger correctement la sécurité des exceptions chaque fois que vous utilisez un objet.

Le plus souvent, vous devez gérer des objets alloués dynamiquement, des nombres d'objets dynamiques, etc. Dans le bloc try, certains codes peuvent créer de nombreux objets (leur nombre est déterminé au moment de l'exécution) et stocker des pointeurs sur ceux-ci dans une liste. Maintenant, ce n'est pas un scénario exotique, mais très commun. Dans ce cas, vous voudriez écrire des choses comme

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

Bien sûr, la liste elle-même sera détruite si elle sort du cadre, mais cela ne nettoierait pas les objets temporaires que vous avez créés.

Au lieu de cela, vous devez emprunter la route laide:

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

Aussi: pourquoi les langues même gérées offrent-elles un blocage final malgré les ressources désallouées automatiquement par le ramasse-miettes de toute façon?

Astuce: vous pouvez faire plus avec "finalement" qu'une simple désallocation de mémoire.

7
Mephane

Une autre émulation de bloc "enfin" utilisant les fonctions lambda 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();
}

Espérons que le compilateur optimisera le code ci-dessus.

Maintenant nous pouvons écrire un code comme celui-ci:

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 vous le souhaitez, vous pouvez envelopper cet idiome dans des macros "essayez - enfin":

// 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 :(

Maintenant, le bloc "finally" 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

Personnellement, je n'aime pas la version "macro" du langage "enfin" et préfère utiliser la fonction pure "with_finally" même si la syntaxe est plus volumineuse dans ce cas.

Vous pouvez tester le code ci-dessus ici: http://coliru.stacked-crooked.com/a/1d88f64cb27b381

PS

Si vous avez besoin d'un bloc finally dans votre code, les macros portées ou ON_FINALLY/ON_EXCEPTION seront probablement mieux adaptés à vos besoins. .

Voici un court exemple d'utilisation 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(); });

    ...
7
anton_rh

Comme indiqué dans les autres réponses, C++ peut prendre en charge une fonctionnalité de type finally. L’implémentation de cette fonctionnalité qui est probablement la plus proche du langage standard est celle qui accompagne C++ Core Guidelines , un ensemble de bonnes pratiques d’utilisation du C++ éditées par Bjarne Stoustrup et Herb Sutter. Un implémentation de finally fait partie de Guidelines Support Library (GSL). Dans les instructions, il est recommandé d’utiliser finally pour traiter les interfaces de style ancien. Il existe également une instruction propre, intitulée tilisez un objet final_action pour exprimer le nettoyage si aucun descripteur de ressource approprié n’est disponible. .

Ainsi, non seulement C++ prend en charge finally, il est en fait recommandé de l'utiliser dans de nombreux cas d'utilisation courants.

Un exemple d'utilisation de l'implémentation GSL ressemblerait à ceci:

#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.
}

L'implémentation et l'utilisation de GSL sont très similaires à celles de réponse de Paolo.Bolzoni . Une différence est que l'objet créé par gsl::finally() n'a pas l'appel disable(). Si vous avez besoin de cette fonctionnalité (par exemple, pour renvoyer la ressource une fois qu'elle est assemblée et qu'aucune exception ne surviendra inévitablement), vous préférerez peut-être l'implémentation de Paolo. Sinon, l'utilisation de GSL est aussi proche que possible de l'utilisation de fonctionnalités standardisées.

5
tobi_s

FWIW, Microsoft Visual C++ prend en charge try, enfin, et il a toujours été utilisé dans les applications MFC comme méthode d’attrapage des exceptions graves qui, autrement, entraîneraient un blocage. Par exemple;

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

J'ai déjà utilisé cela par le passé pour sauvegarder des sauvegardes de fichiers ouverts avant de quitter. Certains paramètres de débogage JIT briseront ce mécanisme.

5
SmacL

Pas vraiment, mais vous pouvez les imiter dans une certaine mesure, par exemple:

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;

Notez que le bloc finally peut lui-même renvoyer une exception avant que l'exception d'origine ne soit émise de nouveau, supprimant ainsi l'exception d'origine. C'est exactement le même comportement que dans un Java finally-block. De plus, vous ne pouvez pas utiliser return à l'intérieur des blocs try & catch.

3
bcmpinc

Je suis arrivé avec une macro finally qui peut être utilisée presque comme ¹ le mot clé finally en Java; il utilise std::exception_ptr et amis, les fonctions lambda et std::promise, de sorte qu'il nécessite C++11 ou supérieur; il utilise également l’extension expression composée GCC, également prise en charge par clang.

WARNING: un version antérieure de cette réponse utilisait une implémentation différente du concept, avec de nombreuses autres limitations.

Premièrement, définissons une classe d'assistance.

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

Ensuite, il y a la macro réelle.

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

Il peut être utilisé comme ceci:

void test() {
    try_with_finally {
        raise_exception();
    }    

    catch(const my_exception1&) {
        /*...*/
    }

    catch(const my_exception2&) {
        /*...*/
    }

    finally {
        clean_it_all_up();
    }    
}

L'utilisation de std::promise le rend très facile à implémenter, mais cela introduit probablement aussi un surcoût inutile qui pourrait être évité en ne réimplémentant que les fonctionnalités nécessaires de std::promise.


¹ CAVEAT: il y a quelques choses qui ne fonctionnent pas tout à fait comme la version Java de finally. Du haut de ma tête:

  1. il n'est pas possible de rompre avec une boucle externe avec l'instruction break à l'intérieur des blocs try et catch(), car ils vivent dans une fonction lambda;
  2. il doit y avoir au moins un bloc catch() après le try: il s'agit d'une exigence de C++;
  3. si la fonction a une valeur de retour autre que void mais qu'il n'y a pas de retour dans les blocs try et catch()'s, la compilation échouera car la macro finally deviendra un code qui voudra renvoyer une void. Cela pourrait être, euh, un void en ayant une macro finally_noreturn.

Dans l’ensemble, je ne sais pas si j’utiliserais ce matériel moi-même, mais c’était amusant de jouer avec. :)

3
Fabio A.

J'ai un cas d'utilisation où je pense que finallydevrait est une partie parfaitement acceptable du langage C++ 11, car je pense qu'il est plus facile à lire d'un point de vue fluide. Mon cas d'utilisation est une chaîne de threads consommateur/producteur, dans laquelle une sentinelle nullptr est envoyée à la fin de l'exécution pour arrêter tous les threads.

Si C++ le supporte, vous voudrez que votre code ressemble à ceci:

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

Je pense que c'est plus logique que de placer votre déclaration finale au début de la boucle, car elle survient après la fin de la boucle ... mais c'est un voeu pieux car nous ne pouvons pas le faire en C++. Notez que la file d'attente downstream est connectée à un autre thread, vous ne pouvez donc pas insérer la sentinelle Push(nullptr) dans le destructeur de downstream car elle ne peut pas être détruite à ce stade .. .il doit rester en vie jusqu'à ce que l'autre thread reçoive la nullptr.

Alors, voici comment utiliser une classe RAII avec lambda pour faire la même chose:

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> callback_;
    };

et voici comment vous l'utilisez:

    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);
        }
    }
2
Mark Lakata

Comme beaucoup de gens l’ont dit, la solution consiste à utiliser les fonctionnalités de C++ 11 pour éviter enfin les blocages. Une des fonctionnalités est unique_ptr .

Voici la réponse de Mephane écrite en utilisant des modèles 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);
}
_

Une autre introduction à l’utilisation de unique_ptr avec les conteneurs C++ Standard Library est ici

1
Mark Lakata

Je voudrais fournir une alternative.

Si vous voulez enfin que bloc soit toujours appelé, il suffit de le mettre après le dernier bloc catch (qui devrait probablement être catch( ... ) pour capturer une exception inconnue)

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 vous souhaitez enfin bloquer comme dernière chose à faire lorsqu'une exception est levée, vous pouvez utiliser une variable locale booléenne. Avant de l'exécuter, définissez-la sur false et affectez la valeur true à la toute fin du bloc try, puis, après le blocage du bloc, vérifiez la variable. valeur:

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();
}
1
jave.web