The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
This section explains the preconditions that the callback passed to
make_scope_guard
is subject to. They are hopefully all intuitive, with the
possible exception of void return.
- invocable with no arguments
- void return
- nothrow-invocable
- nothrow-destructible if non-reference template argument
- const-invocable if const reference template argument
- appropriate lifetime if lvalue reference template argument
- movable or copyable if non-reference template argument
The callback MUST be invocable with no arguments. Additional arguments are intentionally not supported. The client MAY use a capturing lambda to easily pass something that takes arguments in its original form.
This precondition is enforced at compile time.
void my_release(Resource& r) noexcept;
sg::make_scope_guard(my_release); // ERROR: which resource?
sg::make_scope_guard(my_release, my_resource); // ERROR: 1 arg only, please
sg::make_scope_guard([&my_resource]() noexcept
{ my_release(my_resource); }); // OK
The callback MUST return void. Returning anything else is intentionally rejected. The user MAY wrap their call in a lambda that ignores the return value.
This precondition is enforced at compile time.
bool foo() noexcept;
sg::make_scope_guard(foo); // ERROR: does not return void
sg::make_scope_guard([]() noexcept {/*bool ignored =*/ foo();}); // OK
The callback SHOULD NOT throw when invoked. Marking callbacks noexcept
is
RECOMMENDED. Throwing from a callback that is associated with an active
scope guard when it goes out of scope results in a call to std::terminate
.
Clients MAY use a lambda to wrap something that throws in a try-catch
block,
choosing to deal with or ignore exceptions.
By default, this precondition is not enforced at compile time. That can be changed when using ≥C++17.
bool throwing() { throw std::runtime_error{"attention"}; }
sg::make_scope_guard([]() noexcept {
try { throwing(); } catch(...) { /* taking the blue pill */ }
});
If the template argument Callback
is not a reference, then the callback
MUST NOT throw upon destruction. The user MAY use a reference if necessary:
This precondition is enforced at compile time.
struct throwing
{
~throwing() noexcept(false) { throw std::runtime_error{"some error"}; }
void operator()() noexcept {}
};
try
{
throwing_dtor tmp;
sg::make_scope_guard([&tmp](){ tmp(); })
// guard destroyed, tmp still alive
} // tmp only destroyed here
catch(...) { /* handle somehow */ }
If the callback is const
, it MUST be const-invocable. In practice, that
means that an appropriate const
operator()
respecting the other
preconditions MUST exist.
This precondition is enforced at compile time.
struct Foo
{
Foo() {} // (need user provided ctor)
void operator()() const noexcept { }
} const foo;
auto guard = sg::make_scope_guard(foo); // OK, foo const with const op()
If the template argument is an lvalue reference, then the function argument MUST be valid at least until it is not associated with any active scope guard. Notice this is the case when the template argument is deduced from both lvalues and lvalue references.
This precondition is not enforced at compile time.
If the template argument is not a reference, then it MUST be either copyable or movable (or both). This is the case when the template argument is deduced from rvalues and rvalue references.
This precondition is enforced at compile time.