I want to implement a finally
block in my C++ program, and the language certainly has the tools to do it if not a native facility. I was wondering what the best way
This simple implementation seems to be 100% safe.
template< typename t >
class sentry {
t o;
public:
sentry( t in_o ) : o( std::move( in_o ) ) {}
sentry( sentry && ) = delete;
sentry( sentry const & ) = delete;
~ sentry() noexcept {
static_assert( noexcept( o() ),
"Please check that the finally block cannot throw, "
"and mark the lambda as noexcept." );
o();
}
};
template< typename t >
sentry< t > finally( t o ) { return { std::move( o ) }; }
The noexcept
is important because you don't want to throw an exception when your function is already exiting due to an exception. (That leads to immediate termination.) C++ doesn't check that the lambda really can't throw anything; you manually check and mark it noexcept
. (See below.)
The factory function is necessary because otherwise there's no way to obtain a type dependent on a lambda.
The copy and move constructors must be deleted because they could be used to implicitly generate a temporary object, which would implement another sentry, which would call the block prematurely upon destruction. But the default assignment operator is left intact because if you already have two sentries that do different things, it's OK to assign them. (Somewhat theoretical, but whatever.)
It would be nice if the constructor were explicit
, but that seems to preclude in-place initialization of the return value. Since the class is not movable, the object living in the caller's scope must be initialized directly by the expression in the return
statement.
To use, just define a guard like this:
auto && working_state_guard = finally( [&]() noexcept {
reset_working_state();
} );
It's essential to bind to a reference, because declaring a real object in the calling scope would require move-initializing that object from the function return value.
Circa version 4.7, g++ -Wall
would give a warning that the guard is unused. Whether or not you're coding against that, you can add a little safety and documentation at the end of the function with an idiom:
static_cast< void >( working_state_guard );
This lets the reader know about execution of code from the beginning of the scope, and it can serve as a reminder to double-check when copy-pasting code.
int main() {
auto && guard = finally( []() noexcept {
try {
std::cout << "Goodbye!\n";
} catch ( ... ) {
// Throwing an exception from here would be worse than *anything*.
}
} );
std::cin.exceptions( std::ios::failbit );
try {
float age;
std::cout << "How old are you?\n";
std::cin >> age;
std::cout << "You are " << age << " years (or whatever) old\n";
} catch ( std::ios::failure & ) {
std::cout << "Sorry, didn't understand that.\n";
throw;
}
static_cast< void >( guard );
}
This produces output like
$ ./sentry
How old are you?
3
You are 3 years (or whatever) old.
Goodbye!
$ ./sentry
How old are you?
four
Sorry, didn't understand that.
Goodbye!
terminate called after throwing an instance of 'std::ios_base::failure'
what(): basic_ios::clear
Abort trap: 6
Looking at some of the "previous attempts," I see a transactional commit()
method. I don't think that belongs in the ScopeGuard/finally block implementation. Implementing a protocol is the responsibility of the contained functor, so the correct division of labor would be to encapsulate a Boolean flag in it, such as by capturing a bool
local flag, and flipping the flag when the transaction is complete.
Likewise trying to cancel the action by reassigning the functor itself is just a messy approach. Generally prefer an extra case inside the existing protocol, to inventing a new protocol around the old one.
An alternative solution using std::function. No factory function needed. No per-use template instantiation (better footprint?!). No std::move and && stuff needed, no auto needed ;)
class finally
{
std::function<void()> m_finalizer;
finally() = delete;
public:
finally( const finally& other ) = delete;
finally( std::function<void()> finalizer )
: m_finalizer(finalizer)
{
}
~finally()
{
std::cout << "invoking finalizer code" << std::endl;
if( m_finalizer )
m_finalizer();
}
};
Usage:
int main( int argc, char * argv[] )
{
bool something = false;
try
{
try
{
std::cout << "starting" << std::endl;
finally final([&something]() { something = true; });
std::cout << "throwing" << std::endl;
throw std::runtime_error("boom");
}
catch(std::exception & ex )
{
std::cout << "inner catch" << std::endl;
throw;
}
}
catch( std::exception & ex )
{
std::cout << "outer catch" << std::endl;
if( something )
{
std::cout << "works!" << std::endl;
}
else
{
std::cout << "NOT working!" << std::endl;
}
}
std::cout << "exiting" << std::endl;
return 0;
}
Output:
starting
throwing
invoking finalizer code
inner catch
outer catch
works!
exiting