2010-09-08 4 views
21

Ich habe eine kleine Utility-Klasse für C++ 11 geschrieben, die ich als Scope-Guard für eine einfachere Handhabung der Ausnahmesicherheit und ähnlicher Dinge verwende.C++ 11 Scope Exit Guard, eine gute Idee?

Scheint etwas wie ein Hack. Aber ich bin überrascht, dass ich es nirgendwo anders mit C++ 11 Funktionen gesehen habe. Ich denke Boost hat etwas ähnliches für C++ 98.

Aber ist es eine gute Idee? Oder gibt es potenzielle Probleme, die ich verpasst habe? Gibt es bereits eine ähnliche Lösung (mit C++ 11-Funktionen) in Boost oder ähnlich?

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) 

und es ist so etwas wie verwendet.

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

Fest destructor – ronag

+0

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

+2

Vielleicht möchten Sie sich meine Lazy-RAII-Klassen ansehen: http://stackoverflow.com/questions/2419650/cc-macro-template-blackmagic-to-generate-unique-name/2419715#2419715. Beachten Sie, dass Ihr 'scope_exit' auf die Beseitigung von Kopierkonstruktoren angewiesen ist. Wenn Sie dieses Snippet ohne diese Optimierung kompilieren, rufen Sie den Scope-Exit Lambda zweimal auf. Sehen Sie sich meine RAII-Klassen an, um dieses Problem zu umgehen. –

Antwort

16

Aber ist es eine gute Idee?

Sicher. Ein verwandtes Thema ist die RAII paradigm.

Oder gibt es mögliche Probleme, die ich verpasst habe?

Sie behandeln keine Ausnahmen.

Ist es bereits eine ähnliche Lösung (mit ++ 0x-Features C) im Boost oder ähnlichem?

Alexandrescu kam mit ScopeGuard eine lange Zeit zurück. Sowohl Boost als auch std::tr1 haben eine Sache namens scoped_ptr und shared_ptr (mit einem benutzerdefinierten Deleter), mit dem Sie genau dies erreichen können.

+0

Wo gehe ich nicht mit Ausnahmen um? EDIT: Ofc der Destruktor. – ronag

+0

Btw, scoped_ptr wurde nicht zu tr1 hinzugefügt. Ich glaube? – ronag

+0

Es war nicht. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf – Potatoswatter

7

Zielfernrohre sind definitiv eine gute Idee. Ich denke das Scope Guard Konzept ist ein potentes Werkzeug für die Ausnahmesicherheit. Wenn Sie eine sicherere, sauberere Version von Boost ScopeExit mit C++ 0x Syntax machen können, denke ich, es wäre Ihre Zeit wert.

Ähnlich wie Alexandrescu ScopeGuard und Boost ScopeExit, die D programming language hat direkte Syntax für diese Art von Sache. Das D-Programmierungsteam hielt den Scope Guard für eine gute Idee, dass sie ihn directly to the language hinzufügten (dh er ist nicht in einer Bibliothek implementiert).

Beispiel.

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!"); 
} 

Die auf dem Oszilloskop basierenden Wächter sind nichts Neues. Seine Funktionalität kann mit einem Klassen-Destruktor (RAII und all das) einfach repliziert werden. Es ist auch möglich mit try/finally in C# oder Java zu ersetzen. Heck, sogar Pthreads bietet einen rudimentären Schutzumfang, genannt pthread_cleanup_push.

Was Umfang Wachen so mächtig macht, ist, wenn Sie mehrere scope(*)-Anweisungen in der Funktion. Es skaliert unglaublich gut, im Gegensatz zu try/finally, die super menschliche Kräfte erfordern, um mehr als zwei zu verwalten.

4

Wenn create_scope_exit durch einen binären Operator ersetzen, können wir entfernen Klammern:

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)); 
    } 
}; 

Verbrauch:

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

upd:
Entsprechende Makro:

#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

Das Problem mit dieser Version ist, dass Sie für jeden "at_scope_exit" einen eindeutigen Namen erstellen müssen. – ronag

+0

@ronag meinst du "für jeden atScopeExit"? Sie können ein Makro mit __LINE__ wie in Ihrem Code verwenden. – Abyx

0

Die Kobold lementation könnte sehr vereinfacht werden tr1::function und tr1::unique_ptr verwenden, wie unten:

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

Ich sehe nicht, wie das eine Vereinfachung ist? Es leidet auch unter dem Problem, dass der Benutzer einen eindeutigen Namen für jede Instanz aufstellen muss (was der gesamte Zweck der Makros in der Implementierung in der Frage ist). – ronag

+0

Und was ist der Zweck von std :: unique_ptr hier? Scheint mir, dass du genauso gut verzichten könntest. – ronag

+0

Vielleicht hast du Pongba missverstanden, der ScopeGuard ist nützlich mit der std :: -Funktion (in dieser Zeit war es tr1 :: function). Wenn Sie Chinesisch lesen können, lesen Sie den Beitrag (http://mindhacks.cn/2012/08/27/modern-cpp-practices/) oder übersetzen Sie ihn mit google. – jtianling

-1

my $ 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

Wir konnten die hässlichen [&] Sachen weglassen, indem sie es in der Definition setzen:

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

Dann:

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

Getestet mit MSVC++ 11.0 (VS2012). Grüße.

0

Dies ist eine gute Idee, aber es gibt ein paar Probleme mit Ihrer Klasse.

  1. sollten Sie den neuen Operator deaktivieren (Sie nicht wollen, um den Benutzer brauchen es so zu verwenden, dass Kräfte, die auf diese löschen nennen, nicht wahr?)
  2. Sie benötigen ein „commit“ -Funktion um, denn dies ist ein scope guard anstelle einer einfachen RAII

Hinweis sein, dass Sie Punkt implementieren, wenn 2 Sie einen aussagekräftigen Namen müssen für jede Scopeguard Sie instanziieren. Dies ist im Allgemeinen kein Problem, aber es könnte in Ihrer Anwendung (oder nach Ihrem Geschmack) sein.

Schließlich wäre diese Frage wahrscheinlich passender für CodeReview gewesen.

0

Mit 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 }); 

Verbrauch:

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