C ++ prend-il en charge les blocs « enfin »?
Qu'est-ce que l' idiome RAII ?
Quelle est la différence entre l'idiome RAII de C ++ et l'instruction 'using' de C # ?
C ++ prend-il en charge les blocs « enfin »?
Qu'est-ce que l' idiome RAII ?
Quelle est la différence entre l'idiome RAII de C ++ et l'instruction 'using' de C # ?
Réponses:
Non, C ++ ne prend pas en charge les blocs «enfin». La raison en est que C ++ prend en charge RAII: "L'acquisition de ressources est l'initialisation" - un mauvais nom † 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 sera appelé lorsque le bloc dans lequel il a été créé se termine - même lorsque ce bloc est quitté en présence d'une exception. Voici l'explication de Bjarne Stroustrup sur le sujet.
Une utilisation courante de RAII est le verrouillage d'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èrent des ressources, vous pouvez utiliser un destructeur très simple, peut-être même par défaut, pour la classe propriétaire, car il n'a pas besoin de gérer manuellement les durées de vie de ses ressources membres. . (Merci à Mike B de l' avoir signalé.)
Pour ceux qui connaissent C # ou VB.NET, vous pouvez reconnaître que RAII est similaire à la destruction déterministe .NET en utilisant 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 l'implémentation d'IDisposable dans .NET (même le langage .NET C ++ / CLI), 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 façon déterministe; la mémoire n'est libérée que pendant les cycles de récupération de place.
† Certaines personnes croient que «la destruction est l'abandon des ressources» est un nom plus précis pour l'idiome RAII.
En C ++, le finalement n'est PAS requis en raison de RAII.
RAII déplace la responsabilité de la sécurité des exceptions de l'utilisateur de l'objet vers le concepteur (et le réalisateur) de l'objet. Je dirais que c'est le bon endroit car vous n'avez alors besoin d'obtenir une sécurité d'exception correcte qu'une seule fois (dans la conception / mise en œuvre). En utilisant enfin, vous devez obtenir une sécurité d'exception correcte à chaque fois que vous utilisez un objet.
IMO aussi le code semble plus net (voir ci-dessous).
Exemple:
Un objet de base de données. Pour vous assurer que la connexion DB est utilisée, elle doit être ouverte et fermée. En utilisant RAII, cela peut être fait dans le constructeur / destructeur.
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 DB correctement. L'objet DB se fermera correctement par l'utilisation d'un destructeur, peu importe comment nous essayons de l'abuser.
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 qu'il incombe à l'utilisateur de l'objet de fermer correctement la connexion DB. Vous pouvez maintenant faire valoir que cela peut être fait dans le finaliseur, mais les ressources peuvent avoir une disponibilité limitée ou d'autres contraintes et donc vous voulez généralement contrôler la libération de l'objet et ne pas compter sur le comportement non déterministe du garbage collector.
C'est aussi 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
// Make sure not to throw exception if one is already propagating.
Il est important que les destructeurs C ++ ne lèvent pas d'exceptions également pour cette raison.
RAII est généralement meilleur, mais vous pouvez facilement avoir la sémantique enfin en C ++. En utilisant une petite quantité de code.
En outre, les directives de base C ++ donnent enfin.
Voici un lien vers l' implémentation GSL Microsoft et un lien vers l' implémentation Martin Moene
Bjarne Stroustrup a déclaré à plusieurs reprises que tout ce qui se trouve dans le GSL signifiait éventuellement entrer dans la norme. Ce devrait donc être une manière pérenne de l'utiliser enfin .
Vous pouvez facilement vous implémenter si vous le souhaitez, continuez à lire.
En C ++ 11 RAII et lambdas permettent de faire enfin un général:
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é cela plusieurs fois pour garantir la fermeture du descripteur de fichier POSIX dans un programme C ++.
Avoir une vraie classe qui gère les ressources et évite ainsi tout type de fuite est généralement mieux, mais cela est finalement utile dans les cas où créer une classe sonne comme une surpuissance.
En outre, je l'aime mieux que les autres langages enfin parce que s'il est utilisé naturellement, vous écrivez le code de fermeture à proximité du code d'ouverture (dans mon exemple, le nouveau et supprimer ) et la destruction suit la construction dans l'ordre LIFO comme d'habitude en C ++. Le seul inconvénient est que vous obtenez une variable automatique que vous n'utilisez pas vraiment et 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 le repos 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 de désactivation est utile si le finalement doit être appelé uniquement en cas d'échec. Par exemple, vous devez copier un objet dans trois conteneurs différents, vous pouvez configurer enfin pour annuler chaque copie et désactiver une fois toutes les copies réussies. Ce faisant, si la destruction ne peut pas être lancée, vous garantissez une garantie solide.
exemple de désactivation :
//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 enfin l' avoir , mais le code devient un peu plus long. Définissez simplement une structure avec seulement un constructeur et un destructeur, le constructeur prend des références à tout ce qui est nécessaire et le destructeur fait 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"; }
FinalAction
c'est essentiellement le même que l' ScopeGuard
idiome populaire , mais avec un nom différent.
Au-delà de faciliter le nettoyage avec des objets basés sur la pile, RAII est également utile car le même nettoyage «automatique» se produit 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 dtor de cette classe est appelé en conséquence.
Cela signifie que lorsque vous atteignez le nirvana RAII et que tous les membres d'une classe utilisent RAII (comme des 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 il n'a pas besoin de gérer manuellement son durée de vie des ressources des membres.
pourquoi est-ce que même les langues gérées fournissent un bloc final malgré les ressources qui sont désallouées automatiquement par le garbage collector de toute façon?
En fait, les langages basés sur les récupérateurs ont besoin "enfin" de plus. Un garbage collector ne détruit pas vos objets en temps opportun, il ne peut donc pas être utilisé pour nettoyer correctement les problèmes non liés à la mémoire.
En termes de données allouées dynamiquement, beaucoup diraient que vous devriez utiliser des pointeurs intelligents.
Toutefois...
RAII transfère la responsabilité de la sécurité d'exception de l'utilisateur de l'objet au concepteur
Malheureusement, c'est sa propre chute. Les vieilles habitudes de programmation C meurent dur. Lorsque vous utilisez une bibliothèque écrite en C ou dans un style très C, RAII n'aura pas été utilisé. À moins de réécrire l'intégralité du front-end de l'API, c'est exactement ce avec quoi vous devez travailler. Ensuite, le manque de «enfin» mord vraiment.
CleanupFailedException
. Existe-t-il un moyen plausible d'obtenir un tel résultat en utilisant RAII?
SomeObject.DoSomething()
méthode et voudra savoir si (1) elle a réussi, (2) a échoué sans effets secondaires , (3) a échoué avec des effets secondaires auxquels l'appelant est prêt à faire face , ou (4) a échoué avec des effets secondaires auxquels l'appelant ne peut pas faire face. Seul l'appelant saura à quelles situations il peut et ne peut pas faire face; ce dont l'appelant a besoin est un moyen de savoir quelle est la situation. Il est dommage qu'il n'y ait pas de mécanisme standard pour fournir les informations les plus importantes sur une exception.
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 du code comme ceci:
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 "essayer - 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 "enfin" 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" de l'idiome "finalement" et préférerais utiliser la fonction pure "with_finally" même si une syntaxe est plus volumineuse dans ce cas.
Vous pouvez tester le code ci-dessus ici: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Si vous avez besoin d'un bloc enfin dans votre code, les gardes de portée ou les macros ON_FINALLY / ON_EXCEPTION répondront probablement mieux à 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(); });
...
Désolé d'avoir déterré un tel ancien thread, mais il y a une erreur majeure dans le raisonnement suivant:
RAII déplace la responsabilité de la sécurité des exceptions de l'utilisateur de l'objet vers le concepteur (et le réalisateur) de l'objet. Je dirais que c'est le bon endroit car vous n'avez alors besoin d'obtenir une sécurité d'exception correcte qu'une seule fois (dans la conception / mise en œuvre). En utilisant enfin, vous devez obtenir une sécurité d'exception correcte à chaque fois que vous utilisez un objet.
Plus souvent qu'autrement, vous devez traiter des objets alloués dynamiquement, des nombres dynamiques d'objets, etc. Dans le bloc d'essai, certains codes peuvent créer de nombreux objets (combien sont déterminés au moment de l'exécution) et leur stocker des pointeurs dans une liste. Maintenant, ce n'est pas un scénario exotique, mais très courant. Dans ce cas, vous voudriez écrire des trucs 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 lors de la sortie de la portée, mais cela ne nettoiera pas les objets temporaires que vous avez créés.
Au lieu de cela, vous devez emprunter la voie 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 est-ce que même les langages gérés fournissent un bloc final malgré les ressources qui sont de toute façon automatiquement désallouées par le garbage collector?
Astuce: il y a plus que vous pouvez faire avec "finalement" plus qu'une simple désallocation de mémoire.
new
ne renvoie pas NULL, il lève une exception à la place
std::shared_ptr
et std::unique_ptr
directement dans le stdlib.
FWIW, Microsoft Visual C ++ prend en charge try, enfin et il a toujours été utilisé dans les applications MFC comme méthode pour intercepter des exceptions graves qui entraîneraient sinon un plantage. 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 utilisé cela dans le passé pour faire des choses comme enregistrer des sauvegardes de fichiers ouverts avant de quitter. Certains paramètres de débogage JIT vont cependant briser ce mécanisme.
Comme indiqué dans les autres réponses, C ++ peut prendre en charge des finally
fonctionnalités similaires. L'implémentation de cette fonctionnalité qui est probablement la plus proche de faire partie du langage standard est celle qui accompagne les C ++ Core Guidelines , un ensemble de meilleures pratiques pour l'utilisation du C ++ édité par Bjarne Stoustrup et Herb Sutter. Une implémentation definally
fait partie de la bibliothèque de support des directives (GSL). Tout au long des directives, l'utilisation de finally
est recommandée lorsque vous traitez avec des interfaces à l'ancienne, et il a également sa propre directive, intitulée Utiliser 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 à:
#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 la réponse de Paolo.Bolzoni . Une différence est que l'objet créé par gsl::finally()
n'a pas l' disable()
appel. Si vous avez besoin de cette fonctionnalité (par exemple, pour retourner la ressource une fois qu'elle est assemblée et qu'aucune exception ne se produira), vous préférerez peut-être l'implémentation de Paolo. Sinon, l'utilisation de GSL est aussi proche que possible des fonctionnalités standardisées.
Pas vraiment, mais vous pouvez les émuler 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 finalement peut lui-même lever une exception avant que l'exception d'origine ne soit renvoyée, rejetant ainsi l'exception d'origine. C'est exactement le même comportement que dans un bloc Java finalement. De plus, vous ne pouvez pas utiliser return
à l'intérieur des blocs try & catch.
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
bloc.
J'ai créé une finally
macro qui peut être utilisée presque comme ¹ le finally
mot - clé en Java; il utilise std::exception_ptr
et les amis, les fonctions lambda et std::promise
, donc il nécessite C++11
ou au-dessus; il utilise également l' extension de l' expression composée GCC, qui est également prise en charge par clang.
AVERTISSEMENT : une version antérieure de cette réponse utilisait une implémentation différente du concept avec de nombreuses autres limitations.
Définissons d'abord 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
rend très facile à implémenter, mais il introduit probablement aussi un peu de surcharge inutile qui pourrait être évitée en réimplémentant uniquement les fonctionnalités nécessaires à partir de std::promise
.
¹ CAVEAT: il y a quelques choses qui ne fonctionnent pas comme la version java de finally
. Du haut de ma tête:
break
déclaration à partir du try
et des catch()
blocs de, car ils vivent dans une fonction lambda;catch()
bloc après try
: c'est une exigence C ++;try
et catch()'s
, la compilation échouera car la finally
macro se développera en code qui voudra retourner a void
. Cela pourrait être, euh, un vide en ayant une finally_noreturn
sorte de macro.Dans l'ensemble, je ne sais pas si j'utiliserais ce truc moi-même, mais c'était amusant de jouer avec. :)
catch(xxx) {}
bloc impossible au début de la finally
macro, où xxx est un type faux uniquement dans le but d'avoir au moins un bloc catch.
catch(...)
, non?
xxx
dans un espace de noms privé qui ne sera jamais utilisé.
J'ai un cas d'utilisation où je pense que finally
devrait être une partie parfaitement acceptable du langage C ++ 11, car je pense qu'il est plus facile à lire d'un point de vue de flux. Mon cas d'utilisation est une chaîne de threads consommateur / producteur, où une sentinelle nullptr
est envoyée à la fin de la course pour fermer tous les threads.
Si C ++ le supportait, vous voudriez 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 mettre votre déclaration finally au début de la boucle, car cela se produit après la sortie de la boucle ... mais c'est un vœu 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 mettre la sentinelle push(nullptr)
dans le destructeur de downstream
car elle ne peut pas être détruite à ce stade ... elle doit rester en vie jusqu'à ce que l'autre thread reçoive lenullptr
.
Voici donc comment utiliser une classe RAII avec lambda pour faire de même:
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);
}
}
Comme beaucoup de gens l'ont dit, la solution consiste à utiliser les fonctionnalités C ++ 11 pour éviter enfin les blocages. L'une des fonctionnalités est unique_ptr
.
Voici la réponse de Méphane é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 de bibliothèque standard C ++ est ici
Je voudrais proposer une alternative.
Si vous voulez enfin que le bloc soit toujours appelé, placez-le juste après le dernier bloc catch (qui devrait probablement être catch( ... )
pour intercepter 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 voulez enfin bloquer comme dernière chose à faire quand une exception est levée, vous pouvez utiliser une variable locale booléenne - avant de l'exécuter, définissez-la sur false et placez la véritable affectation à 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();
}
Je pense également que RIIA n'est pas un remplacement pleinement utile pour la gestion des exceptions et pour avoir enfin. BTW, je pense aussi que RIIA est un mauvais nom tout autour. J'appelle ces types de concierges de classe et je les utilise beaucoup. 95% du temps, ils n'initialisent ni n'acquièrent de ressources, ils appliquent certains changements sur une base limitée ou prennent quelque chose de déjà configuré et s'assurent qu'ils sont détruits. Ceci étant le nom officiel du modèle obsédé par Internet, je suis abusé pour avoir même suggéré que mon nom pourrait être meilleur.
Je ne pense pas qu'il soit raisonnable d'exiger que chaque configuration compliquée d'une liste de choses ad hoc doive avoir une classe écrite pour la contenir afin d'éviter des complications lors du nettoyage de tout en ayant besoin d'attraper plusieurs types d'exception en cas de problème dans le processus. Cela conduirait à de nombreuses classes ad hoc qui ne seraient tout simplement pas nécessaires autrement.
Oui, c'est bien pour les classes conçues pour gérer une ressource particulière, ou pour les classes génériques conçues pour gérer un ensemble de ressources similaires. Mais, même si toutes les choses impliquées ont de tels wrappers, la coordination du nettoyage peut ne pas être une simple invocation dans l'ordre inverse des destructeurs.
Je pense qu'il est parfaitement logique pour C ++ d'avoir enfin un. Je veux dire, jeez, tellement de morceaux ont été collés dessus au cours des dernières décennies qu'il semble que des gens étranges deviendraient soudainement conservateurs sur quelque chose comme finalement ce qui pourrait être très utile et probablement rien de plus compliqué que d'autres choses qui ont été ajouté (bien que ce soit juste une supposition de ma part.)
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
ne fait pas.