Comment puis-je résumer un motif de capture d’essais répété en C ++?

J’ai un motif qui se répète pour plusieurs fonctions membres qui ressemble à ceci:

int myClass::abstract_one(int sig1) { try { return _original->abstract_one(sig1); } catch (std::exception& err) { handleException(err); } catch (...) { handleException(); } } bool myClass::abstract_two(int sig2) { try { return _original->abstract_two(sig2); } catch (std::exception& err) { handleException(err); } catch (...) { handleException(); } } [...] int myClass::abstract_n(bool sig3a, short sig3b) { try { return _original->abstract_n(sig3a, sig3b); } catch (std::exception& err) { handleException(err); } catch (...) { handleException(); } } 

Où abstract one à n sont les méthodes d’une interface abstraite virtuelle pure pour lesquelles myClass et _original sont des implémentations concrètes.

Je n’aime pas que ce modèle se répète dans le code et aimerait trouver un moyen d’éliminer le modèle et le code répétitifs try / catch en une seule abstraction, mais je ne peux pas penser à un bon moyen de faire cela en C ++ sans les macros. Je pense qu’il existe un moyen, avec des modèles, de faire cela mieux.

Veuillez suggérer un moyen propre de refactoriser ce code pour résumer le motif répété.

    J’ai posé une question conceptuelle très similaire, voir Est-ce qu’une nouvelle exception est légale dans un “essai” nested? .

    Fondamentalement, vous pouvez déplacer les différents gestionnaires d’exceptions vers une fonction distincte en interceptant toutes les exceptions, en appelant le gestionnaire et en renvoyant à nouveau l’exception active.

     void handle() { try { throw; } catch (std::exception& err) { handleException(err); } catch (MyException& err) { handleMyException(err); } catch (...) { handleException(); } } try { return _original->abstract_two(sig2); } catch (...) { handle(); } 

    Il s’adapte bien avec plusieurs types d’exceptions différents à différencier. Vous pouvez mettre le premier try .. catch(...) dans des macros si vous aimez:

     BEGIN_CATCH_HANDLER return _original->abstract_two(sig2); END_CATCH_HANDLER 

    Une option, s’il y a un nombre limité d’arités de fonction, serait d’utiliser un modèle de fonction:

     template  ReturnT call_and_handle(ClassT* obj, ReturnT(ClassT::*func)()) { try { return (obj->*func)(); } catch (const std::exception& ex) { handleException(ex); } catch (...) { handleException(); } return ReturnT(); } 

    Cela suppose que handleException est une fonction non membre, mais il est facile de la modifier s’il s’agit d’une fonction membre. Vous devez décider quel call_and_handle retourne si une exception est gérée; Je l’ai renvoyant un ReturnT initialisé en tant ReturnT réservé.

    Cela réduit vos fonctions de membre à:

     int myClass::abstract_one() { return call_and_handle(_original, &myClass::abstract_one); } 

    Vous auriez besoin d’un modèle de fonction distinct pour appeler des fonctions comportant un paramètre, deux parameters, etc.

    Si vous avez des fonctions qui ont un nombre compliqué de parameters et que vous êtes vraiment désespéré, vous pouvez utiliser une macro (je ne recommanderais pas cela, cependant):

     #define CALL_AND_HANDLE(expr) \ try { \ return (expr); \ } \ catch (const std::exception& ex) { \ handleException(ex); \ } \ catch (...) { \ handleException(); \ } 

    Qui peut être utilisé comme:

     int myClass::abstract_one() { CALL_AND_HANDLE(_original->myClass::abstract_one()); } 

    En passant, si vous catch (...) et ne revenez pas sur l’exception capturée, vous devez dans la plupart des cas mettre fin au programme.

    En variante de la solution d’Alexander Gessler, vous pouvez omettre certaines des accolades qui rendent cette implémentation un peu longue. Il fait exactement la même chose, mais avec un peu moins de {} verbage.

     void handle() try { throw; } catch (std::exception& err) { handleException(err); } catch (MyException& err) { handleMyException(err); } catch (...) { handleException(); } int myClass::abstract_one(int sig1) try { return _original->abstract_one(sig1); } catch (...) { handle(); return -1; } 

    Ma réponse est conceptuellement similaire à celle de James McNellis, sauf que j’utilise boost :: bind pour faire le gros du travail:

     using boost::bind; class myClass { public: myClass(origClass * orig) : orig_(orig) {} int f1(bool b) { return wrapper(bind(&origClass::f1, orig_, b)); } bool f2(int i) { return wrapper(bind(&origClass::f2, orig_, i)); } void f3(int i) { return wrapper(bind(&origClass::f3, orig_, i)); } private: origClass * orig_; template  typename T::result_type wrapper(T func) { try { return func(); } catch (std::exception const &e) { handleError(e); } catch (...) { handleError(); } } }; 

    Notez que je n’utiliserais pas de fonction boost :: ici car cela pourrait gêner l’inline.

    Je n’ai pas de réponse, sauf pour suggérer qu’il vaudrait peut-être mieux éviter la gestion des exceptions et compter plutôt sur Smart Pointers et Boost Scope Exit pour nettoyer toutes vos ressources. De cette façon, vous ne devrez pas capturer les exceptions à moins de pouvoir faire quelque chose à leur sujet, ce qui est rarement le cas. Ensuite, vous pouvez effectuer toute la gestion des exceptions dans un endroit centralisé plus haut dans la chaîne d’appel pour signaler les erreurs, etc.

    Utilisez boost :: function et boost :: bind. Fonctionne avec n’importe quelle signature de fonction tant que le type de retour correspond;

     #include  #include  using boost::function; using boost::bind; template T exception_wrapper(boost::function func) { try { return func(); } catch (std::exception& err) { handleException(err); } catch (...) { handleException(); } } // ways to call int result = exception_wrapper(bind(libraryFunc, firstParam)); // or a member function LibraryClass* object; result = exception_wrapper(bind(&LibraryClass::Function, object, firstParam)); // So your wrapping class: class YourWrapper : public SomeInterface { public: int abstract_one(int sig1) { return exception_wrapper(bind(&LibraryClass::concrete_one, m_pObject, sig1)); } bool abstract_two(int sig1, int sig2) { return exception_wrapper(bind(&LibraryClass::concrete_two, m_pObject, sig1, sig2)); } // ... private: LibraryClass* m_pObject; }; 

    Ma réponse est: ne faites rien du tout. Le premier exemple de code comme indiqué est correct. Alors qu’est-ce qu’il y a la répétition? C’est clair et clair, et fait ce que ça ressemble. Il peut être compris sans aucun fardeau mental supplémentaire après le code vu et avec une connaissance générale de C ++.

    Considérez votre motivation pour poser cette question.

    D’où je viens, ce sont des projets antérieurs où le code devait être examiné par d’autres personnes – pas des experts en PhD Comp Sci, mais des inspecteurs fédéraux, des ingénieurs mécaniciens, des programmeurs autodidactes, des scientifiques, etc. eux), mais seul un doctorat en dôme chromé insolite, ou un jeune programmeur désireux d’impressionner tout le monde avec son salut de QI, apprécierait les “solutions” intelligentes à cette question. Pour les endroits où je suis allé, rien ne vaut un code clair et clair qui fait ce qu’il dit, sans avoir à garder à l’esprit le sens de dizaines de classes, macros, etc. et à reconnaître le “modèle de conception” à proprement parler. ingénieurs logiciels expérimentés.

    De plus en plus, le code C ++ (et Java et C #) devient de plus en plus sophistiqué au niveau du codage, mais doit être compris par des non-experts en C ++.

    Bien sûr, YMMV, en fonction du public cible et de la source des futurs programmeurs de maintenance. Parfois, un codage intelligent est nécessaire pour atteindre certains objectives. Votre cas est-il un exemple?