La gestion des exceptions en C++ est limitée à try/throw/catch. Contrairement à Object Pascal, Java, C # et Python, même en C++ 11, la construction finally
n'a pas été implémentée.
J'ai vu énormément de littérature C++ discutant du "code d'exception". Lippman écrit que le code sécurisé d'exception est un sujet important mais avancé et difficile, au-delà de la portée de son introduction - ce qui semble impliquer que le code sécurisé n'est pas fondamental pour C++. Herb Sutter consacre 10 chapitres au sujet dans son exceptionnel C++!
Pourtant, il me semble que bon nombre des problèmes rencontrés lors de la tentative d'écriture de "code de sécurité d'exception" pourraient être assez bien résolus si la construction finally
était implémentée, permettant au programmeur de s'assurer que même en cas d'exception , le programme peut être restauré dans un état sûr, stable et sans fuite, à proximité du point d'allocation des ressources et du code potentiellement problématique. En tant que programmeur Delphi et C # très expérimenté, j'utilise try .. finalement bloque assez largement dans mon code, comme le font la plupart des programmeurs dans ces langages.
Compte tenu de tous les "cloches et sifflets" mis en œuvre dans C++ 11, j'ai été étonné de constater que "finalement" n'était toujours pas là.
Alors, pourquoi la construction finally
n'a-t-elle jamais été implémentée en C++? Ce n'est vraiment pas un concept très difficile ou avancé à comprendre et va beaucoup pour aider le programmeur à écrire du "code d'exception".
Il s'agit simplement de comprendre la philosophie et les idiomes du C++. Prenez votre exemple d'une opération qui ouvre une connexion à une base de données sur une classe persistante et doit s'assurer qu'elle ferme cette connexion si une exception est levée. C'est une question de sécurité d'exception et s'applique à tout langage avec des exceptions (C++, C #, Delphi ...).
Dans un langage qui utilise try
/finally
, le code pourrait ressembler à ceci:
database.Open();
try {
database.DoRiskyOperation();
} finally {
database.Close();
}
Simple et direct. Il existe cependant quelques inconvénients:
finally
, sinon je perds des ressources.DoRiskyOperation
est plus qu'un appel de méthode unique - si j'ai du traitement à faire dans le bloc try
- alors l'opération Close
peut finir par être un peu décente l'opération Open
. Je ne peux pas écrire mon nettoyage juste à côté de mon acquisition.try
/finally
.L'approche C++ ressemblerait à ceci:
ScopedDatabaseConnection scoped_connection(database);
database.DoRiskyOperation();
Cela résout complètement tous les inconvénients de l'approche finally
. Il présente quelques inconvénients, mais ils sont relativement mineurs:
ScopedDatabaseConnection
vous-même. Cependant, c'est une implémentation très simple - seulement 4 ou 5 lignes de code.Personnellement, compte tenu de ces avantages et inconvénients, je trouve RAII (L'acquisition de ressources est l'initialisation) une technique bien préférable à finally
. Votre kilométrage peut varier.
Enfin, parce que RAII est un idiome bien établi en C++, et pour soulager les développeurs d'une partie du fardeau de l'écriture de nombreux Scoped...
classes, il existe des bibliothèques comme ScopeGuard et Boost.ScopeExit qui facilitent ce type de nettoyage déterministe.
Parce que C++ prend en charge une alternative presque toujours meilleure: la technique "l'acquisition de ressources est l'initialisation" (TC++ PL3 section 14.4). L'idée de base est de représenter une ressource par un objet local, afin que le destructeur de l'objet local libère la ressource. De cette façon, le programmeur ne peut pas oublier de libérer la ressource.
La raison pour laquelle C++ n'a pas finally
est parce qu'il n'est pas nécessaire en C++. finally
est utilisé pour exécuter du code, qu'une exception se soit produite ou non, qui est presque toujours une sorte de code de nettoyage. En C++, ce code de nettoyage devrait être dans le destructeur de la classe appropriée et le destructeur sera toujours appelé, tout comme un bloc finally
. L'idiome de l'utilisation du destructeur pour votre nettoyage s'appelle RAII .
Au sein de la communauté C++, il peut être plus question de code "sans exception", mais il est presque tout aussi important dans d'autres langages qui ont des exceptions. L'intérêt du code "à l'exception des exceptions" est que vous pensez dans quel état votre code est laissé si une exception se produit dans l'une des fonctions/méthodes que vous appelez.
En C++, le code "sans exception" est légèrement plus important, car C++ n'a pas de récupération de place automatique qui prend en charge les objets qui restent orphelins en raison d'une exception.
La raison pour laquelle la sécurité des exceptions est plus discutée dans la communauté C++ vient probablement du fait qu'en C++, vous devez être plus conscient de ce qui peut mal tourner, car il y a moins de filets de sécurité par défaut dans le langage.
D'autres ont discuté du RAII comme solution. C'est une très bonne solution. Mais cela ne résout pas vraiment pourquoi ils n'ont pas ajouté finally
aussi, car c'est une chose largement souhaitée. La réponse à cette question est plus fondamentale pour la conception et le développement de C++: tout au long du développement de C++, les personnes impliquées ont fortement résisté à l'introduction de fonctionnalités de conception qui peuvent être obtenues en utilisant d'autres fonctionnalités sans énormément de bruit et surtout lorsque cela nécessite l'introduction. de nouveaux mots clés qui pourraient rendre l'ancien code incompatible. Étant donné que RAII fournit une alternative hautement fonctionnelle à finally
et que vous pouvez réellement rouler votre propre finally
en C++ 11 de toute façon, il n'y avait pas grand-chose à faire.
Il vous suffit de créer une classe Finally
qui appelle la fonction passée à son constructeur dans son destructeur. Ensuite, vous pouvez le faire:
try
{
Finally atEnd([&] () { database.close(); });
database.doRisky();
}
Cependant, la plupart des programmeurs C++ natifs préfèrent en général les objets RAII bien conçus.
Vous pouvez utiliser un modèle "trap" - même si vous ne voulez pas utiliser le bloc try/catch.
Mettez un objet simple dans la portée requise. Dans le destructeur de cet objet, mettez votre logique "finale". Quoi qu'il en soit, lorsque la pile est déroulée, le destructeur d'objet sera appelé et vous obtiendrez votre bonbon.
Eh bien, vous pouvez trier votre propre finally
, en utilisant Lambdas, qui obtiendrait ce qui suit pour compiler correctement (en utilisant un exemple sans RAII bien sûr, pas le plus beau morceau de code):
{
FILE *file = fopen("test","w");
finally close_the_file([&]{
cout << "We're closing the file in a pseudo-finally clause." << endl;
fclose(file);
});
}
Voir cet article .