View on GitHub

scope_guard

A modern C++ scope guard that is easy to use but hard to misuse.

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.

Preconditions in detail

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

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.

Compile time enforcement:

This precondition is enforced at compile time.

Example:
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

void return

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.

Compile time enforcement:

This precondition is enforced at compile time.

Example:
bool foo() noexcept;
sg::make_scope_guard(foo); // ERROR: does not return void
sg::make_scope_guard([]() noexcept {/*bool ignored =*/ foo();}); // OK

nothrow-invocable

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.

Compile time enforcement:

By default, this precondition is not enforced at compile time. That can be changed when using ≥C++17.

Example:
bool throwing() { throw std::runtime_error{"attention"}; }
sg::make_scope_guard([]() noexcept {
  try { throwing(); } catch(...) { /* taking the blue pill */ }
});

nothrow-destructible if non-reference template argument

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:

Compile time enforcement:

This precondition is enforced at compile time.

Example:
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 */ }

const-invocable if const reference

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.

Compile time enforcement:

This precondition is enforced at compile time.

Example:
  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()

appropriate lifetime if lvalue reference

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.

Compile time enforcement:

This precondition is not enforced at compile time.

movable or copyable if non-reference

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.

Compile time enforcement:

This precondition is enforced at compile time.