bon usage de std :: uncaught_exception dans un destructeur

Certains articles concluent que “ne lève jamais d’exception d’un destructeur” et que “std :: uncaught_exception () n’est pas utile”, par exemple:

  • http://www.gotw.ca/gotw/047.htm (écrit par Herb Sutter)

Mais il semble que je ne comprends pas le point. J’ai donc écrit un petit exemple de test (voir ci-dessous).

Étant donné que tout va bien avec l’exemple de test, j’apprécierais beaucoup certains commentaires sur ce qui pourrait ne pas être faux

résultats des tests:

./principale

     Foo :: ~ Foo (): exception interceptée - mais avec une exception en attente - ignorant
     int main (int, char **): exception capturée: à partir de int Foo :: bar (int)

./main 1

     Foo :: ~ Foo (): exception interceptée - mais * aucune * exception n'est en attente - renvoi
     int main (int, char **): exception interceptée: de Foo :: ~ Foo ()

Exemple:

// file main.cpp // build with eg "make main" // tested successfully on Ubuntu-Karmic with g++ v4.4.1 #include  class Foo { public: int bar(int i) { if (0 == i) throw(std::ssortingng("from ") + __PRETTY_FUNCTION__); else return i+1; } ~Foo() { bool exc_pending=std::uncaught_exception(); try { bar(0); } catch (const std::ssortingng &e) { // ensure that no new exception has been created in the meantime if (std::uncaught_exception()) exc_pending = true; if (exc_pending) { std::cerr << __PRETTY_FUNCTION__ << ": caught exception - but have pending exception - ignoring" << std::endl; } else { std::cerr << __PRETTY_FUNCTION__ << ": caught exception - but *no* exception is pending - rethrowing" << std::endl; throw(std::string("from ") + __PRETTY_FUNCTION__); } } } }; int main(int argc, char** argv) { try { Foo f; // will throw an exception in Foo::bar() if no arguments given. Otherwise // an exception from Foo::~Foo() is thrown. f.bar(argc-1); } catch (const std::string &e) { std::cerr << __PRETTY_FUNCTION__ << ": caught exception: " << e << std::endl; } return 0; } 

ADDED : En d’autres termes: malgré les avertissements dans certains articles, cela fonctionne comme prévu – alors, qu’est-ce qui ne va pas?

Il n’y a rien techniquement faux avec votre code. Il est parfaitement sûr de ne jamais terminer accidentellement parce que vous avez lancé une exception alors que cela n’était pas sûr. Le problème est que cela n’est pas non plus utile, dans la mesure où il ne lève parfois pas non plus d’exception quand il est sécuritaire de le faire. La documentation de votre destructeur doit essentiellement dire “cela peut ou ne peut pas lever une exception.”

Si de temps en temps il ne lève pas d’exception, vous pouvez également ne jamais lancer d’exception . De cette façon, vous êtes au moins cohérent.

Herb Sutter fait référence à un problème différent. Il parle de:

 try { } catch (...) { try { // here, std::uncaught_exception() will return true // but it is still safe to throw an exception because // we have opened a new try block } catch (...) { } } 

Le problème est donc que si std::uncaught_exception() renvoie true, vous ne savez pas avec certitude si vous pouvez ou non lancer une exception en toute sécurité. Vous finissez par avoir à éviter de lancer une exception lorsque std::uncaught_exception() renvoie true juste pour être sûr.

Herb Sutter parle de la situation dans laquelle un object de classe T est détruit alors qu’il existe une exception non interceptée dans un object de classe U std::uncaught_exception() renverrait true dans le destructeur T Le destructeur serait incapable de savoir s’il est appelé pendant le déroulement de la stack. Si c’est le cas, il ne doit pas être jeté, sinon c’est comme si de rien n’était.

La classe U aurait un problème en utilisant la classe T dans le destructeur. U se retrouverait face à un object T inutile qui refuserait toute action risquée dans son destructeur (pouvant inclure l’écriture d’un fichier journal ou la validation d’une transaction dans une firebase database).

Herb Sutter suggère de ne jamais jeter de destructeur, ce qui est une bonne idée. Cependant, le C ++ 17 offre une autre option. Il a introduit std::uncaught_exceptions() , qui peut être utilisé pour savoir si le destructeur peut lancer. L’exemple suivant montre le problème s’il est résolu en mode C ++ 14. Si compilé en mode C ++ 17, cela fonctionnerait correctement.

 #include  #include  #include  class T { public: ~T() noexcept(false) { #if __cplusplus >= 201703L // C++17 - correct check if (std::uncaught_exceptions() == uncaught_exceptions_) #else // Older C++ - incorrect check if (!std::uncaught_exception()) #endif { throw (std::ssortingng{__PRETTY_FUNCTION__} + " doing real work"); } else { std::cerr << __PRETTY_FUNCTION__ << " cowardly quitting\n"; } } private: #if __cplusplus >= 201703L const int uncaught_exceptions_ {std::uncaught_exceptions()}; #endif }; class U { public: ~U() { try { T t; } catch (const std::ssortingng &e) { std::cerr << __PRETTY_FUNCTION__ << " caught: " << e << '\n'; } } }; int main() { try { U u; throw (std::string{__PRETTY_FUNCTION__} + " threw an exception"); } catch (const std::string &e) { std::cerr << __PRETTY_FUNCTION__ << " caught: " << e << '\n'; } return 0; }