Охрана выхода C++11, хорошая идея?
Я написал небольшой класс утилит для C++11, который я использую в качестве защиты области видимости для облегчения обработки безопасности исключений и подобных вещей.
Похоже, как взломать. Но я удивлен, что не видел этого где-то еще, используя функции C++11. Я думаю, что в Boost есть что-то похожее для C++98.
Но хорошая ли это идея? Или есть потенциальные проблемы, которые я пропустил? Есть ли уже подобное решение (с функциями C++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;
}
11 ответов
Но хорошая ли это идея?
Конечно. Смежная тема - парадигма RAII.
Или есть потенциальные проблемы, которые я пропустил?
Вы не обрабатываете исключения.
Есть ли уже подобное решение (с функциями C++0x) в boost или подобное?
Александреску придумал ScopeGuard давно. И Буст, и std::tr1
есть вещь под названием scoped_ptr
а также shared_ptr
(с пользовательским средством удаления), который позволяет вам сделать это.
Охрана прицела определенно хорошая идея. Я думаю, что концепция охраны границ является мощным инструментом для обеспечения безопасности исключений. Если вы можете сделать более безопасную, более чистую версию, чем Boost's 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 обеспечивает элементарную защиту области, называемую http://linux.die.net/man/3/pthread_cleanup_push.
Что делает охранники прицела такими мощными, когда у вас есть несколько scope(*)
операторы в функции. Это невероятно хорошо масштабируется, в отличие от try/finally
которые требуют сверхчеловеческих способностей управлять чем-то большим, чем два.
Если заменить 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__]
Я использовал очень легкое решение для макросов:
#define defer(code) std::unique_ptr<void, void (*)(void*)> _defer##__LINE__((void*)1, [&](void*) { code });
Используйте это как:
{
defer({ code_to_execute(); })
}
Реализация может быть очень упрощена с помощью 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));
}
Мы могли бы опустить уродливые вещи [&], поместив их в определение:
#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). С уважением.
Это хорошая идея, но у вас есть пара проблем с вами, класс.
- Вы должны отключить новый оператор (вы не хотите, чтобы пользователь использовал его таким образом, чтобы принудительно вызывать delete для этого, верно?)
- вам нужна функция "commit", для того чтобы она была защитой области видимости вместо простого RAII
обратите внимание, что если вы реализуете пункт 2, вам нужно осмысленное имя для каждого созданного вами сторожа защиты. В общем, это не проблема, но это может быть в вашем приложении (или на ваш вкус).
Наконец, этот вопрос, вероятно, был бы более подходящим для CodeReview.
Используя 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);
});
...
}
Мои 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; } );