scope_guard и on_exception_guard для освобождения ресурсов
Для обеспечения нужного уровня безопасности исключений бывает необходимо при выходе из области видимости выполнить какой-то код (например, освободить ресурс) даже, если код прерван исключением. При этом не всегда удобно создавать отдельный объект, управляющий ресурсом, например, если идет работа с чисто C-шным кодом. Для этого я предлагаю стандартизировать scope_guard и on_exception_guard, со вспомогательными методами их создания make_scope_guard() make_on_exception_guard().
int main()
{
int fd = ::open( "/etc/passwd", O_RDWR );
if( fd == -1 )
exit( EXIT_FAILURE );
auto fd_guard = make_scope_guard( [=](){ ::close( fd ); } );
// make_scope_guard() создает объект scope_guard, в деструкторе которого выполнится переданный код
do_somth_with( fd );
close( fd ); // если закрывать fd до выхода из области видимости не нужно,
fd_guard.release(); // тогда эти две строки можно вообще не писать
do_some_more();
return 0;
}
При использовании такого кода в реальности оптимизитор выбросит все ненужное, в т.ч. и создание объекта scope_guard, при этом сгенерирует код, в котором при любом выходе из области видимости будет вызван close().
При вызове make_scope_guard(), создается объект scope_guard и ему передается лямбда для выполнения в деструкторе ~scope_guard(). Также у scope_guard есть метод release() для отмены выполнения кода в деструкторе и для симметричности acquire(), хотя не уверен, что он нужен. Примерная реализация:
template <typename T>
struct scope_guard
{
scope_guard( T code )
:code( code )
,own( true )
{}
scope_guard( scope_guard&& other )
:code( other.code )
,own( true )
{ other.own = false; }
scope_guard& operator=( scope_guard&& other ) = delete;
~scope_guard() { if( own ) code(); }
scope_guard( const scope_guard& ) = delete;
scope_guard& operator=( const scope_guard& ) = delete;
void release() { own = false; }
void acquire() { own = true; }
T code;
bool own;
};
/**
* @code{.cpp}
* auto fd_guard = make_scope_guard( [=](){ ::close( fd ); } );
* @endcode
*/
template <typename T>
scope_guard<T> make_scope_guard( T code )
{
return scope_guard<T>( code );
}
У такого подхода есть один мелкий недочет - при создании scope_guard может вылететь исключение, однако, это возможно только при копировании захватываемых переменных по значению, однако в реальности все сколько-нибудь нетривиальные переменные scope_guard будет захватывать по ссылке.
on_exception_guard отличается от scope_guard лишь тем, что выполняет переданный код в деструкторе только при выходе из области видимости по исключению. В нужности методов release(), а тем более acquire() я сильно сомневаюсь. Реализация:
template <typename T>
struct on_exception_guard
{
on_exception_guard( T code )
:code( code )
{}
~on_exception_guard()
{
#ifdef __cpp_lib_uncaught_exceptions
if ( std::uncaught_exceptions() > 0 )
#else
if ( std::uncaught_exception() )
#endif
code();
}
T code;
};
template <typename T>
on_exception_guard<T> make_on_exception_guard( T code )
{
return on_exception_guard<T>( code );
}
P.S. Можно обсуждать имена методов и типов, их функциональность и реализацию, но, по-моему, бесспорно, что нечто с функциональностью scope_exit давно пора стандартизировать.
Если такое и делать, то тогда release внутри должен не просто переключать флаг, а еще и выполнять код. Тогда, вероятно это будет работать. Но при этом нужно будет помнить, что вне функции .release освобождать ресурс нельзя. И тогда, можно спокойно забывать писать функцию release и guard вызовет code при своем уничтожении