Защита выхода из области С++ 11, хорошая идея?

Я написал небольшой служебный класс для C++11, который я использую в качестве защиты области действия для упрощения обработки безопасности исключений и подобных вещей.

Похоже на взлом. Но я удивлен, что не видел его где-то еще с использованием функций С++ 11. Я думаю, что у boost есть что-то подобное для C++98.

Но хорошая ли это идея? Или есть потенциальные проблемы, которые я пропустил? Есть ли уже подобное решение (с функциями С++ 11) в boost или аналогичном?

    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)

и он используется что-то вроде.

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

person ronag    schedule 08.09.2010    source источник
comment
см. pizer.wordpress.com/2008/11/ 22/scope-guards-revisited-c0x-style   -  person sellibitze    schedule 10.09.2010
comment
Возможно, вы захотите взглянуть на мои классы ленивого RAII: " title="шаблон макроса cc blackmagic для создания уникального имени"> stackoverflow.com/questions/2419650/ . Обратите внимание, что ваш scope_exit зависит от исключения конструктора копирования. Если вы скомпилируете этот фрагмент без этой оптимизации, вы дважды вызовете лямбда-выход из области видимости. Посмотрите мои занятия по RAII, чтобы узнать, как обойти эту проблему.   -  person Johannes Schaub - litb    schedule 30.09.2010
comment
Я думаю, что мой файл будет закрыт, когда он выйдет за рамки. Это уже должно быть безопасно для исключений.   -  person emsr    schedule 08.07.2011
comment
Вот простая реализация защиты области С++ 11, которая не зависит от устранения конструктора копирования и не рискует оборванными указателями на временные объекты: stackoverflow.com/a/12545195/558823   -  person Stephan Tolksdorf    schedule 22.09.2012
comment
Другая потенциальная проблема заключается в использовании вами зарезервированных имен для ваших макросов...   -  person Zaxter    schedule 04.09.2017


Ответы (9)


Но хорошая ли это идея?

Конечно. Связанной темой является парадигма RAII.

Или есть потенциальные проблемы, которые я пропустил?

Вы не обрабатываете исключения.

Есть ли уже подобное решение (с функциями С++ 0x) в boost или аналогичном?

Александреску давно придумал ScopeGuard. И в Boost, и в std::tr1 есть вещь под названием scoped_ptr и < href="http://msdn.microsoft.com/en-us/library/bb982026.aspx" rel="noreferrer">shared_ptr (с пользовательским средством удаления), который позволяет вам выполнить именно это.

person dirkgently    schedule 08.09.2010
comment
Где я не могу обрабатывать исключения? РЕДАКТИРОВАТЬ: Ofc деструктор. - person ronag; 08.09.2010
comment
Кстати, scoped_ptr не был добавлен в tr1, я полагаю? - person ronag; 08.09.2010
comment
Обновлено. Я пропустил shared_ptr материал. - person dirkgently; 08.09.2010
comment
это был не Александреску, это был Петру Маргинян. drdobbs.com/cpp/generic-change- то, как вы пишете, за исключением/ - person Stefano Falasca; 19.07.2013

Для справки: Boost ScopeExit.

person Gregory Pakosz    schedule 08.09.2010

Охранники области, безусловно, хорошая идея. Я думаю, что концепция защиты области действия является мощным инструментом для обеспечения безопасности исключений. Если вы можете сделать более безопасную и чистую версию, ScopeExit с использованием синтаксиса C++0x, я думаю, это стоит вашего времени.

Подобно ScopeGuard от Александреску и ScopeExit от Boost, Язык программирования D имеет прямой синтаксис для подобных вещей. Команда разработчиков D решила, что защита области действия была достаточно хорошей идеей, поэтому они добавили ее непосредственно в язык (т.е. он не реализован в библиотеке).

Пример.

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

Охранники на основе прицела не являются чем-то новым. Его функциональность может быть легко воспроизведена с помощью деструктора класса (RAII и все такое). Также можно заменить на try/finally в C# или Java. Черт возьми, даже pthreads предоставляет элементарную защиту области видимости, называемую pthread_cleanup_push.

Что делает защиту области действия такой мощной, так это наличие нескольких операторов scope(*) в функции. Он невероятно хорошо масштабируется, в отличие от try/finally, которые требуют сверхчеловеческих способностей, чтобы управлять чем-то большим, чем два.

person deft_code    schedule 01.10.2010

Если заменить create_scope_exit бинарным оператором, мы можем убрать скобки:

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

Использование:

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

upd:
Соответствующий макрос:

#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__]
person Abyx    schedule 14.06.2011
comment
Проблема с этой версией в том, что вам нужно придумать уникальное имя для каждого at_scope_exit. - person ronag; 14.06.2011
comment
@ronag вы имеете в виду для каждого atScopeExit? Вы можете использовать макрос с LINE как в своем коде. - person Abyx; 15.06.2011

Для справки: в TS 3 есть scope_exit.

person Geoffroy    schedule 11.06.2020

Реализация может быть очень упрощена с использованием tr1::function и tr1::unique_ptr, как показано ниже:

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));
}
person Weipeng L    schedule 18.10.2011
comment
Я не понимаю, как это упрощение? Он также страдает от проблемы, заключающейся в том, что пользователю необходимо придумать уникальное имя для каждого экземпляра (что является всей целью макросов в реализации, представленной в вопросе). - person ronag; 18.10.2011
comment
И какова цель использования std::unique_ptr здесь? Мне кажется, что с таким же успехом можно было бы обойтись и без него. - person ronag; 18.10.2011
comment
Возможно, вы неправильно поняли pongba, ScopeGuard полезен с std::function (в то время это был tr1::function). Если вы умеете читать по-китайски, вы можете прочитать сообщение (mindhacks.cn/2012/ 27/08/modern-cpp-practices) или переведите его с помощью Google. - person jtianling; 23.10.2015

Мы могли бы опустить уродливые [&] вещи, поместив их в определение:

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

Потом:

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

Протестировано с помощью MSVC++ 11.0 (VS2012). С Уважением.

person bobef    schedule 14.11.2012

Это хорошая идея, но есть пара проблем с вашим классом.

  1. вы должны отключить новый оператор (вы не хотите, чтобы пользователь использовал его таким образом, что это заставляет вызывать удаление, верно?)
  2. вам нужна функция «зафиксировать», чтобы это было защита области вместо простого RAII

обратите внимание, что если вы реализуете пункт 2, вам нужно осмысленное имя для каждого экземпляра области видимости, который вы создаете. Это, в общем-то, не проблема, но может быть в вашем приложении (или на ваш вкус).

Наконец, этот вопрос, вероятно, был бы более подходящим для CodeReview.

person Stefano Falasca    schedule 19.07.2013

Использование 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 });

Использование:

if (gdiplus::GdiplusStartup(&token, &startupInput, nullptr) == Gdiplus::Ok) {
    defer({
        gdiplus::GdiplusShutdown(token);
    });
    ...
}
person Steve Fan    schedule 14.01.2018