2010-09-08 4 views
21

J'ai écrit une petite classe utilitaire pour C++ 11 que j'utilise comme une protection de portée pour faciliter la gestion de la sécurité des exceptions et des choses similaires.Garde de sortie C++ 11 portée, une bonne idée?

Cela ressemble un peu à un hack. Mais je suis surpris que je ne l'ai pas vu ailleurs en utilisant les fonctionnalités C++ 11. Je pense que boost a quelque chose de similaire pour C++ 98.

Mais est-ce une bonne idée? Ou y a-t-il des problèmes potentiels que j'ai ratés? Y a-t-il déjà une solution similaire (avec des fonctionnalités C++ 11) en boost ou similaire?

namespace detail 
    { 
     template<typename T> 
     class scope_exit : boost::noncopyable 
     { 
     public:   
      explicit scope_exit(T&& exitScope) : exitScope_(std::forward<T>(exitScope)){} 
      ~scope_exit(){try{exitScope_();}catch(...){}} 
     private: 
      T exitScope_; 
     };   

     template <typename T> 
     scope_exit<T> create_scope_exit(T&& exitScope) 
     { 
      return scope_exit<T>(std::forward<T>(exitScope)); 
     } 
    } 


#define _UTILITY_EXIT_SCOPE_LINENAME_CAT(name, line) name##line 
#define _UTILITY_EXIT_SCOPE_LINENAME(name, line) _UTILITY_EXIT_SCOPE_LINENAME_CAT(name, line) 
#define UTILITY_SCOPE_EXIT(f) const auto& _UTILITY_EXIT_SCOPE_LINENAME(EXIT, __LINE__) = ::detail::create_scope_exit(f) 

et il est utilisé quelque chose comme.

int main() 
{ 
    ofstream myfile; 
    myfile.open ("example.txt"); 
    UTILITY_SCOPE_EXIT([&]{myfile.close();}); // Make sure to close file even in case of exception 
    myfile << "Writing this to a file.\n"; // Imagine this could throw 
    return 0; 
} 
+0

destructor fixe – ronag

+0

voir http://pizer.wordpress.com/2008/11/22/scope-guards-revisited-c0x- style/ – sellibitze

+2

Vous voudrez peut-être jeter un oeil à mes classes lazy-RAII: http://stackoverflow.com/questions/2419650/cc-macro-template-blackmagic-to-generate-unique-name/2419715#2419715. Notez que votre 'scope_exit' repose sur l'élimination du constructeur de la copie. Si vous compilez cet extrait sans cette optimisation, vous appelez le scope-exit lambda deux fois. Voir mes classes RAII sur la façon de contourner ce problème. –

Répondre

16

Mais est-ce une bonne idée?

Bien sûr. Un sujet connexe est le RAII paradigm.

Ou y a-t-il problèmes potentiels que j'ai manqués?

Vous ne gérez pas les exceptions.

est-il déjà une solution similaire (avec C++ 0x caractéristiques) ou similaire à coup de fouet?

Alexandrescu est arrivé avec ScopeGuard depuis longtemps. Les deux Boost et std::tr1 a une chose appelée scoped_ptr et shared_ptr (avec un suppresseur personnalisé) qui vous permet d'accomplir cela.

+0

Où ne gère-t-on pas les exceptions? EDIT: Ofc le destructeur. – ronag

+0

Btw, scoped_ptr n'a pas été ajouté à tr1 je crois? – ronag

+0

Ce n'était pas le cas. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf – Potatoswatter

7

Les protecteurs de portée sont définitivement une bonne idée. Je pense que le concept de garde-champ est un outil puissant pour la sécurité des exceptions. Si vous pouvez faire une version plus sûre et plus propre que ScopeExit de Boost en utilisant la syntaxe C++ 0x, je pense que cela en vaut la peine.

Semblable à ScopeGuard d'Alexandrescu et ScopeExit de Boost, le D programming language a la syntaxe directe pour ce genre de chose. L'équipe de programmation de D a pensé que le gardien de portée était une bonne idée qu'ils l'ont ajouté directly to the language (c'est-à-dire qu'il n'est pas implémenté dans une bibliothèque).

Exemple.

void foo(bool fail) 
{ 
    scope(exit) 
    { 
     writeln("I'm always printed"); 
    } 

    scope(success) writeln("The function exited normally"); 

    scope(error) 
     writeln("The function exited with an exception."); 

    if(fail) 
     throw new Exception("Die Die Die!"); 
} 

Les gardes basés sur la portée ne sont rien de nouveau. Ses fonctionnalités peuvent facilement être répliquées avec un destructeur de classe (RAII et tout ça). Il est également possible de remplacer par try/finally en C# ou en Java. Heck, même pthreads fournit une garde de portée rudimentaire, appelée pthread_cleanup_push.

Ce qui rend les gardes de portée si puissants est lorsque vous avez plusieurs instructions scope(*) dans la fonction. Il s'adapte incroyablement bien, par opposition à try/finally qui nécessitent des pouvoirs super-humains pour gérer quelque chose de plus de deux.

4

Si remplacer create_scope_exit par un opérateur binaire, on peut supprimer entre parenthèses:

class at_scope_exit 
{ 
    template<typename F> 
    struct scope_exit_fn_holder : boost::noncopyable 
    { 
     scope_exit_fn_holder(F&& f) : f(std::forward<F>(f)) {} 

     F f; 
     ~scope_exit_fn_holder() { f(); } 
    }; 

    template<typename F> 
    friend scope_exit_fn_holder<F> operator==(at_scope_exit, F&& f) 
    { 
     return scope_exit_fn_holder<F>(std::forward<F>(f)); 
    } 
}; 

Utilisation:

auto atScopeExit = at_scope_exit() == [&] 
{ 
    ... 
}; 

màj:
correspondant macro:

#include <boost/preprocessor/cat.hpp> 

#define AT_SCOPE_EXIT auto BOOST_PP_CAT(scopeExit_, __LINE__) = at_scope_exit() == [&] 
#define AT_SCOPE_EXIT_EX(...) auto BOOST_PP_CAT(scopeExit_, __LINE__) = at_scope_exit() == [__VA_ARGS__] 
+0

Le problème avec cette version est que vous devez trouver un nom unique pour chaque "at_scope_exit". – ronag

+0

@ronag voulez-vous dire "pour chaque atScopeExit"? Vous pouvez utiliser une macro avec __LINE__ comme dans votre code. – Abyx

0

Les lutin lementation en utilisant tr1::function et tr1::unique_ptr pourrait être très simplifié, comme ci-dessous:

namespace detail 
{ 
    class ScopeGuard 
    { 
    public: 
     explicit ScopeGuard(std::function<void()> onExitScope) 
      : onExitScope_(onExitScope), dismissed_(false) 
     { } 

     ~ScopeGuard() 
     { 
      try 
      { 
       if(!dismissed_) 
       { 
        onExitScope_(); 
       } 
      } 
      catch(...){} 
     } 

     void Dismiss() 
     { 
      dismissed_ = true; 
     } 
    private: 
     std::function<void()> onExitScope_; 
     bool dismissed_; 

     // noncopyable 
    private: 
     ScopeGuard(ScopeGuard const&); 
     ScopeGuard& operator=(ScopeGuard const&); 
    }; 
} 

inline std::unique_ptr<detail::ScopeGuard> CreateScopeGuard(std::function<void()> onExitScope) 
{ 
    return std::unique_ptr<detail::ScopeGuard>(new detail::ScopeGuard(onExitScope)); 
} 
+1

Je ne vois pas comment c'est une simplification? Il souffre également du problème que l'utilisateur a besoin de trouver un nom unique pour chaque instance (ce qui est l'objectif entier des macros dans l'implémentation fournir dans la question). – ronag

+0

Et quel est le but de l'utilisation de std :: unique_ptr ici? Il me semble que vous pourriez tout aussi bien vous en passer. – ronag

+0

Peut-être que vous étiez mal compris pongba, le ScopeGuard est utile avec la fonction std :: (dans ce temps, c'était tr1 :: function). Si vous pouviez lire le chinois, vous pouvez lire le post (http://mindhacks.cn/2012/08/27/modern-cpp-practices/), ou le traduire avec google. – jtianling

-1

mon 0,02 $

struct at_scope_end 
{ 
    std::function < void() > Action; 

    at_scope_end (std::function < void() > Action) : 
     Action (Action) 
    { 
    } 

    ~at_scope_end() 
    { 
     Action(); 
    } 
}; 

#define AT_SCOPE_END_CAT(x,y) x##y 
#define AT_SCOPE_END_ID(index) AT_SCOPE_END_CAT(__sg, index) 
#define AT_SCOPE_END(expr)  at_scope_end AT_SCOPE_END_ID(__LINE__) ([&]() { expr; }); 
0

On peut omettre le laid [&] des choses en le mettant dans la définition:

#define UTILITY_SCOPE_EXIT(f) const auto& _UTILITY_EXIT_SCOPE_LINENAME(EXIT, __LINE__) = ::detail::create_scope_exit([&]f) 

Ensuite:

UTILITY_SCOPE_EXIT({myfile.close();}); 

Testé avec MSVC++ 11.0 (VS2012). Cordialement.

0

C'est une bonne idée, mais il y a quelques problèmes avec votre classe.

  1. vous devez désactiver le nouvel opérateur (vous ne voulez pas avoir besoin de l'utilisateur de l'utiliser de telle sorte que les forces d'appeler supprimer à ce sujet, non?)
  2. vous avez besoin d'une fonction « commit » , pour que ce soit un scope guard lieu d'un simple RAII

avis que si vous implémentez point 2 vous avez besoin d'un nom significatif pour chaque scopeguard instanciation. Ce n'est en général pas un problème, mais cela pourrait être dans votre application (ou à votre goût).

Enfin, cette question aurait probablement été plus appropriée pour CodeReview.

0

En utilisant Boost:

#include <boost/preprocessor/cat.hpp> 

template<class Fn> 
class ScopeGuardDetails { 
    const Fn m_fn; 
public: 
    constexpr ScopeGuardDetails(Fn &&fn) : m_fn(fn) {} 
    ~ScopeGuardDetails() { m_fn(); } 
}; 
#define ScopeGuardName BOOST_PP_CAT(BOOST_PP_CAT(__scope_guard, _), BOOST_PP_CAT(BOOST_PP_CAT(__LINE__, _), __COUNTER__)) 
#define defer(stmt) const auto ScopeGuardName = [](const auto _fn) { \ 
    return ScopeGuardDetails<decltype(_fn)> { std::move(_fn) }; \ 
}([&] { stmt }); 

Utilisation:

if (gdiplus::GdiplusStartup(&token, &startupInput, nullptr) == Gdiplus::Ok) { 
    defer({ 
     gdiplus::GdiplusShutdown(token); 
    }); 
    ... 
} 
Questions connexes