Arene Base
Fundamental Utilities For Safety Critical C++
Loading...
Searching...
No Matches
scope_guard: An RAII Helper

The scope_guard sub-package provides a facility for ensuring that a cleanup function is run when a scope is exited by whatever means.

The public header is

The Bazel target is

//:scope_guard

Guaranteeing Cleanup

A common problem when interacting with externally managed resources (files, allocated memory, etc) is ensuring that resources are correctly released, or other cleanup correctly done, when a function or scope exits in an unexpected fashion. Most commonly, this "unexpected exit" comes from an exception being thrown, but it can also be due to a break or return statement leading to the normal cleanup code being skipped.

In C++, this is generally handled via classes whose constructors acquire the resources, and destructors release them. This pattern is generally called Resource Aqusition Is Initialization, or RAII. However, there are situations where creating a dedicated class to implement this pattern is overly complex, and simply invoking a function to perform the cleanup operation is all that is needed. The scope_guard sub-package provides a mechanism to assist with this in the form of the arene::base::scope_guard class template, and the associated arene::base::on_scope_exit factory function.

arene::base::scope_guard has a single template parameter: the type of an invocable to store, which must satisfy is_nothrow_invocable_r_v<void, Func>. The constructor then constructs an instance of that invocable from the supplied constructor argument, and the destructor invokes it. This means that an instance of arene::base::scope_guard created at local scope will run the supplied invocable when the scope exits, whether that is because the execution continued off the end of the scope, or the scope was exited via a use of break, continue, return or throw.

Using at_scope_exit Directly

Assume a resource management API like the following:

/// @brief A handle to some opaque system resource.
struct resource_handle {
int value;
};
/// @brief Allocates a system resource.
/// @return resource_handle A handle to the allocated resource.
auto allocate_resource() noexcept -> resource_handle;
/// @brief Frees an already allocated resource.
/// @param handle The handle to the resource to be freed.
/// @post The resource pointed to by @c handle is freed.
void free_resource(resource_handle handle) noexcept;

The caller needs to ensure that there are matched calls to the allocation and free functions, else the resource leaks. The following example shows using at_scope_exit directly to ensure that the free function is always called, even if the scope is exited via an exception.

/// @brief Some operation that might throw.
/// @param value A value needed for the operator.
/// @param handle A handle to the resource to perform the operation on.
/// @throws std::out_of_range if @c value is less than 0.
auto might_throw(int value, resource_handle handle) -> int;
/// @brief Example of using scope_guard directly
/// @param value The value to pass to @c might_throw
void basic_usage(int value) {
resource_handle const handle = allocate_resource();
auto const always_free = arene::base::on_scope_exit([&handle]() noexcept { free_resource(handle); });
might_throw(value, handle);
}

Creating a Named Helper

If a particular form of guard will be used repeatedly, instead of calling at_scope_exit directly, a factory function can be made to give the action a descriptive name and hide the implementation details. The previous example refactored in this manner would look like this:

/// @brief Creates a scope guard which will free the supplied resource.
/// @param handle The resource to free on exit.
/// @return A @c scope_guard which will call @c free_resource on @c handle .
auto free_on_exit(resource_handle const& handle) {
return arene::base::on_scope_exit([&handle]() noexcept { free_resource(handle); });
}
/// @brief Example of using a named scope_guard helper.
/// @param value The value to pass to @c might_throw.
void named_usage(int value) {
resource_handle const handle = allocate_resource();
auto const cleanup = free_on_exit(handle);
might_throw(value, handle);
}

Invoking The Guard Early Or Canceling It

If the function needs to be invoked prior to destroying the guard object, the arene::base::scope_guard::invoke_now member function can be used. Alternatively, if the function should not be called, then the arene::base::scope_guard::cancel function can be used. This can be useful if the resource is being passed to somewhere else that will correctly clean up when it is done:

/// @brief Some function that stores the resource to use later
/// @param handle The handle to store.
void store_handle_for_later(resource_handle handle);
/// @brief Example of using the @c invoke_now and @c cancel functionality.
/// @param value If < 0, frees the handle immediately, otherwise it stores it for later.
void maybe_release_early(int value) {
resource_handle const handle = allocate_resource();
auto cleanup = free_on_exit(handle);
if (value < 0) {
std::cout << "Cleaning up immediately...\n";
cleanup.invoke_now();
} else {
store_handle_for_later(handle);
// We have to call cancel to avoid freeing the resource on scope exit.
cleanup.cancel();
}
}