2010-09-08 12 views
21

He escrito una pequeña clase de utilidad para C++ 11 que utilizo como protector de alcance para un manejo más fácil de la seguridad de excepciones y cosas similares.C++ 11 protector de salida de alcance, ¿una buena idea?

Parece algo así como un truco. Pero estoy sorprendido de que no lo haya visto en otro lado usando las características de C++ 11. Creo que boost tiene algo similar para C++ 98.

¿Pero es una buena idea? ¿O hay problemas potenciales que me he perdido? ¿Ya hay una solución similar (con características C++ 11) en boost o similar?

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) 

y se usa algo así como.

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 fijo – ronag

+0

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

+2

Es posible que desee echar un vistazo a mis clases perezosas de RAII: http://stackoverflow.com/questions/2419650/cc-macro-template-blackmagic-to-generate-unique-name/2419715#2419715. Tenga en cuenta que su 'scope_exit' se basa en la eliminación del constructor de copia. Si compila este fragmento sin esa optimización, llama al scope-exit lambda dos veces. Vea mis clases de RAII sobre cómo solucionar ese problema. –

Respuesta

16

Pero, ¿es una buena idea?

Sure. Un tema relacionado es el RAII paradigm.

¿O hay problemas potenciales que me he perdido?

No maneja las excepciones.

Hay ya una solución similar (con C++ 0x características) en el impulso o similar?

Alexandrescu ha aparecido con ScopeGuard hace mucho tiempo. Tanto Boost como std::tr1 tiene una cosa llamada scoped_ptr y shared_ptr (con un eliminador personalizado) que le permite lograr esto.

+0

¿Dónde no manejo las excepciones? EDITAR: Ofc el destructor. – ronag

+0

Por cierto, scoped_ptr no se ha agregado a tr1, ¿no? – ronag

+0

No fue así. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf – Potatoswatter

7

Las protecciones de alcance son definitivamente una buena idea. Creo que el concepto de protector de alcance es una herramienta potente para la seguridad de excepciones. Si puede hacer una versión más limpia y segura que Boost's ScopeExit utilizando la sintaxis C++ 0x, creo que bien valdría la pena.

Similar a ScopeGuard de Alexandrescu y ScopeExit de Boost, el D programming language tiene sintaxis directa para este tipo de cosas. El equipo de programación de D pensó que el protector de alcance era una buena idea y lo agregaron directly to the language (es decir, no está implementado en una biblioteca).

Ejemplo.

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

Las protecciones basadas en el alcance no son nada nuevo. Su funcionalidad se puede replicar fácilmente con un destructor de clase (RAII y todo eso). También es posible reemplazarlo con try/finally en C# o Java. Diablos, incluso pthreads proporciona un protector de alcance rudimentario, llamado pthread_cleanup_push.

Lo que hace que los protectores de alcance sean tan potentes es cuando tiene múltiples declaraciones scope(*) en la función. Se escala increíblemente bien, a diferencia de try/finally que requieren poderes súper humanos para administrar algo más de dos.

4

Si reemplazar create_scope_exit por un operador binario, podemos eliminar los paréntesis:

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

Uso:

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

UPD:
correspondiente 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

El problema con esta versión es que tiene que encontrar un nombre único para cada "at_scope_exit". – ronag

+0

@ronag ¿te refieres a "para cada atScopeExit"? Puede usar una macro con __LINE__ como en su código. – Abyx

0

Los diablillo lementation podría ser mucho simplifica utilizando tr1::function y tr1::unique_ptr, como a continuación:

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

¿No veo cómo esto es una simplificación? También adolece del problema de que el usuario necesita encontrar un nombre único para cada instancia (que es el propósito completo de las macros en la implementación proporcionada en la pregunta). – ronag

+0

¿Y cuál es el propósito de usar std :: unique_ptr aquí? Me parece que podría prescindir. – ronag

+0

Quizás no comprendió pongba, ScopeGuard es útil con la función std :: (en ese momento, era tr1 :: función). Si puede leer chino, puede leer la publicación (http://mindhacks.cn/2012/08/27/modern-cpp-practices/) o traducirla a google. – jtianling

-1

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

Podríamos omitir la fea cosas [&] poniéndolo en la definición:

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

Entonces:

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

Probado con MSVC++ 11.0 (VS2012). Saludos.

0

Esta es una buena idea, pero hay un par de problemas con su clase.

  1. debe deshabilitar el nuevo operador (que no desea el usuario deberá utilizarlo de tal manera que las fuerzas para llamar a borrar en esto, ¿verdad?)
  2. necesita una "comprometerse" función , a fin de que se trata de un scope guard en lugar de un simple RAII

aviso de que si se implementa el punto 2 se necesita un nombre significativo para cada scopeguard usted instancia. Esto es, en general, no es un problema, pero podría estar en su aplicación (o para su gusto).

Finalmente, esta pregunta probablemente habría sido más apropiada para CodeReview.

0

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

Uso:

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