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.
The public interface consists of a template function to create scope guard objects, a few members of those objects, and one boolean compilation option that can be activated with a preprocessor macro definition.
Here is an outline of the client interface:
The free function template make_scope_guard
resides in namespace sg
and
is the primary element of the public interface – most uses do not require
anything else. It provides a way for clients to create a scope guard object that
is associated with the specified callback.
Scope guards execute their associated callback when they are destroyed, unless they were meanwhile dismissed or moved. Like other variables, scope guards with automatic storage are destroyed when they go out of scope, hence the name.
This function template is SFINAE-friendly.
template<typename Callback>
/* unspecified return type */ make_scope_guard(Callback&& callback)
noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value);
The template and function arguments need to respect certain preconditions. They should all be intuitive to C++ programmers, with the possible exception of precondition 2. They are summarized here and discussed in more detail elsewhere.
- invocable with no arguments
- void return
- nothrow-invocable
- nothrow-destructible if non-reference template argument
- const-invocable if const reference
- appropriate lifetime if lvalue reference
- movable or copyable if non-reference
By default, precondition 3 is not enforced at compile time. Precondition 6 is not enforced at compile time. All other preconditions are enforced at compile time.
A scope guard object is returned with
- the provided callback as associated callback
- active state
As the signature shows, instances of this function template are noexcept
iff
Callback
can be nothrow constructed from Callback&&
(after reference
collapsing). Notice this is always the case if Callback
is a reference type.
const auto guard = sg::make_scope_guard([]() noexcept { /* do stuff */ });
Scope guard objects have some unspecified type that MUST NOT be used as a base class.
- A scope guard object that is in an active state executes its associated callback exactly once when leaving scope.
- A scope guard that is in inactive state does not execute its associated callback
- Type
callback_type
dismiss
function- move constructor
- destructor
- default constructor
- copy constructor
- copy assignment operator
- move assignment operator
Note: Deleted special members cannot be used, but they participate in overload resolution. They are listed here because they are explicitly disallowed and that can be considered as part of the the client's interface.
Template argument deduction allows the underlying callback type to be
automatically derived from the function argument. That type – Callback
in the signature of make_scope_guard
above –
is provided as the member type callback_type
.
typedef Callback callback_type;
using my_cb = typename decltype(guard)::callback_type; /* where guard
is a scope guard object */
Scope guards can be dismissed to cancel callback execution. Dismissed scope guards are valid but useless. They are best regarded as garbage awaiting destruction.
void dismiss() noexcept;
None.
The dismissed scope guard is in inactive state.
noexcept
.
bool do_transaction()
{ // example with early returns
if(!do_step1())
return false;
auto undo = sg::make_scope_guard(rollback);
if(!do_step2());
return false;
if(!do_step3());
return false;
undo.dismiss(); // <-- using dismiss
return true;
}
Objects created with make_scope_guard
can be moved. This
possibility exists mainly to allow initialization with assignment syntax, as in
auto g1 = make_scope_guard(f);
. In general, it allows transferring
scope guarding responsibility: auto g2 = std::move(g1);
.
A scope-guard move transfers the callback and corresponding call responsibility (or lack thereof). Moved-from scope guards are valid but useless. They are best regarded as garbage awaiting destruction.
Standard move constructor.
None.
- The moved-from guard is in inactive state.
- The moved-from guard is associated with an unspecified callback.
- The moved-to guard is in the same activity state as the moved-from guard was in before the move operation (active moved-to iff previously active moved-from).
- The moved-to guard is associated with the callback that was associated with the moved-from guard before the move operation;
noexcept
iff Callback
can be nothrow-constructed from Callback&&
(after reference collapse). Notice this is always the case when Callback
is a
reference type.
{
auto g1 = sg::make_scope_guard([]() noexcept { std::cout << "bla"; });
{
auto g2 = std::move(g1);
} // g2 leaves scope here
std::cout << "ble";
} // g1 leaves scope here
std::cout << "bli";
// prints "blablebli"
Scope guards have a (non-virtual) destructor.
Standard destructor.
None.
- the callback was executed iff the guard was in active state before destruction;
- the guard is no longer valid
noexcept
. This motivates two of the preconditions:
nothrow-invocable and
nothrow-destructible if non-reference.
Non applicable.
If ≥C++17 is used, the preprocessor macro SG_REQUIRE_NOEXCEPT_IN_CPP17
can be defined to require nothrow-invocable callbacks in make_scope_guard
at compile time.
Notice however, that this restricts accepted callback types considerably.
This option is disabled by default and enabling it has no effect unless ≥C++17 is used.
#define SG_REQUIRE_NOEXCEPT_IN_CPP17 // (no effect in <C++17)
#include "scope_guard.hpp"
make_scope_guard([](){}); // ERROR: need noexcept (if >=C++17)
make_scope_guard([]() noexcept {}); // OK