mirror of
https://github.com/boostorg/scope.git
synced 2026-01-19 16:52:08 +00:00
1366 lines
50 KiB
Plaintext
1366 lines
50 KiB
Plaintext
[/
|
|
/ Copyright 2023-2024 Andrey Semashev
|
|
/
|
|
/ Distributed under the Boost Software License, Version 1.0.
|
|
/ (See accompanying file LICENSE_1_0.txt or copy at
|
|
/ https://www.boost.org/LICENSE_1_0.txt)
|
|
/
|
|
/ This document is a part of Boost.Scope library documentation.
|
|
/]
|
|
|
|
[section:scope_guards Scope guards]
|
|
|
|
A scope guard is an object that invokes an arbitrary /action/ function object on destruction. Scope guards are useful for implementing
|
|
actions that need to be reliably performed upon control leaving an execution scope (for example, when returning from a function), which
|
|
is especially helpful for handling exceptions.
|
|
|
|
The wrapped action function object is specified on the scope guard construction and cannot be changed afterwards. Its type must be one
|
|
of:
|
|
|
|
* a user-defined class with a public `operator()` taking no arguments, or
|
|
* an lvalue reference to such class, or
|
|
* an lvalue reference or pointer to a function taking no arguments.
|
|
|
|
Note that if the wrapped function is a reference to a function object, that object must be stored externally to the scope guard and
|
|
must remain valid for the entire lifetime of the scope guard.
|
|
|
|
Some scope guards also support specifying an additional /condition/ function object, which allows for customizing the conditions in
|
|
which the action function object must be called. Condition function objects are discussed in more detail in a
|
|
[link scope.scope_guards.condition_functions later section].
|
|
|
|
Boost.Scope provides four kinds of scope guards, differing in their features and conditions upon which the action function object is
|
|
called, summarised in the table below.
|
|
|
|
[table Scope guard comparison
|
|
[[Feature] [[class_scope_scope_exit]] [[class_scope_scope_success]] [[class_scope_scope_fail]] [[class_scope_defer_guard]]]
|
|
[[Supports a condition function?] [Yes] [Yes] [Yes] [No]]
|
|
[[Invokes action on normal scope exit?] [Yes, by default. Depends on condition.] [Yes] [No] [Yes]]
|
|
[[Invokes action on scope exit due to failure?] [Yes, by default. Depends on condition.] [No] [Yes] [Yes]]
|
|
[[Action may throw?] [Yes, by default. Depends on condition.] [Typically yes] [Typically no] [Yes]]
|
|
[[Can be (de)activated?] [Yes] [Yes] [Yes] [No]]
|
|
[[Move-constructible? (requires function objects to be move-constructible)] [Yes] [Yes] [Yes] [No]]
|
|
[[Has factory function? (C++11-friendly)] [Yes] [Yes] [Yes] [No]]
|
|
]
|
|
|
|
In the table above, the term "failure" is used broadly. What constitutes a failure in a given context is specified by user in
|
|
the form of the condition function object. Most often, a thrown exception is taken as an indication of a failure, but it can be
|
|
changed to, for example, a check for an error code that is being returned from the enclosing function.
|
|
|
|
For [class_scope_scope_exit], there is no notion of "success" or "failure". Rather, the scope guard invokes the action function
|
|
object if the condition returns `true`. By default, if the condition function object is not specified by user, the scope
|
|
guard operates as if the condition always returns `true`, which means, for example, that it will invoke the action whether
|
|
the control leaves the scope normally or due to an exception.
|
|
|
|
Although it is possible to specify arbitrary condition function objects, the intended use case for [class_scope_scope_success]
|
|
is to invoke its action when the scope is left normally (i.e. not because of a failure) and for [class_scope_scope_fail] - to
|
|
invoke its action to handle errors, including exceptions. For this reason, action functions that are used with [class_scope_scope_fail]
|
|
are typically not allowed to throw, as this may cause the program to terminate. This is also a concern with other scope guards,
|
|
such as [class_scope_scope_exit] and [class_scope_defer_guard], which also may invoke their actions due to an exception. It is
|
|
user's responsibility to ensure that scope guard actions don't throw if another exception is being propagated. Generally, it is
|
|
recommended to use scope guards to implement actions that cannot throw and move all operations that may fail to the normal code
|
|
flow.
|
|
|
|
[section:conditional Conditional scope guards: `scope_exit`, `scope_success` and `scope_fail`]
|
|
|
|
#include <``[boost_scope_scope_exit_hpp]``>
|
|
#include <``[boost_scope_scope_success_hpp]``>
|
|
#include <``[boost_scope_scope_fail_hpp]``>
|
|
|
|
The [class_scope_scope_exit], [class_scope_scope_success], and [class_scope_scope_fail] scope guards have a lot of similarities
|
|
in interfaces and capabilities and differ in conditions when they invoke the action function object. As shown in the table above,
|
|
these scope guards support specifying an additional condition function object. By default, [class_scope_scope_success] will
|
|
invoke its action if it is destroyed normally, [class_scope_scope_fail] - if it is destroyed due to an exception being thrown.
|
|
[class_scope_scope_exit] by default operates as if the condition always allows executing the action.
|
|
|
|
[tip Condition function objects will be discussed in more detail in the [link scope.scope_guards.condition_functions next] section.
|
|
Note that there are caveats with detecting whether an exception has been thrown, also discussed in the next section.]
|
|
|
|
Given the different default condition function objects, the different scope guard types are typically useful for different purposes:
|
|
|
|
* [class_scope_scope_exit] is useful for general cleanup tasks, which need to be performed regardless of success or failure of the
|
|
enclosing function.
|
|
* [class_scope_scope_success] can be used for common finalization code that needs to be performed on successful completion of the
|
|
function.
|
|
* [class_scope_scope_fail] is intended for handling errors, for example, to revert a partially complete operation to the previous
|
|
valid state.
|
|
|
|
All scope guards are especially useful if there are multiple return points (both normal and via exception), and when the scope
|
|
guard action is not easy to extract as a function.
|
|
|
|
Let's consider an example of a class that receives network requests, validates them and passes to session objects to which each
|
|
request corresponds. After receiving a request, the class must send a response - either a successful one, with the results of
|
|
processing the request, or an error.
|
|
|
|
// A network request receiver
|
|
class receiver
|
|
{
|
|
// A network connection
|
|
connection m_conn;
|
|
// A collection of sessions, searchable by session id
|
|
std::map< std::string, session > m_sessions;
|
|
|
|
public:
|
|
// Processes a network request and sends a response
|
|
void process_request(request const& req)
|
|
{
|
|
const auto start_time = std::chrono::steady_clock::now();
|
|
boost::scope::scope_exit time_guard([start_time]
|
|
{
|
|
// Log the amount of time it took to process the request
|
|
const auto finish_time = std::chrono::steady_clock::now();
|
|
|
|
std::clog << "Request processed in "
|
|
<< std::chrono::duration_cast< std::chrono::milliseconds >(finish_time - start_time).count()
|
|
<< " ms" << std::endl;
|
|
});
|
|
|
|
response resp;
|
|
|
|
boost::scope::scope_fail failure_guard([&]
|
|
{
|
|
// Disconnect in case of exceptions
|
|
m_conn.disconnect(); // doesn't throw
|
|
});
|
|
|
|
boost::scope::scope_success success_guard([&]
|
|
{
|
|
// Send a response that was populated while processing the request
|
|
m_conn.send_message(resp);
|
|
});
|
|
|
|
// Validate the request and populate the response based on the contents of the request
|
|
if (req.start_line().version > max_supported_version)
|
|
{
|
|
resp.start_line().version = max_supported_version;
|
|
resp.start_line().set_status(protocol_version_not_supported);
|
|
return;
|
|
}
|
|
|
|
resp.start_line().version = req.start_line().version;
|
|
|
|
auto it_cseq = req.headers().find(cseq_header);
|
|
if (it_cseq == req.headers().end())
|
|
{
|
|
resp.start_line().set_status(bad_request);
|
|
resp.set_body(mime::text_plain, "CSeq not specified");
|
|
return;
|
|
}
|
|
|
|
resp.headers().add(cseq_header, *it_cseq);
|
|
|
|
auto it_session_id = req.headers().find(session_id_header);
|
|
if (it_session_id == req.headers().end())
|
|
{
|
|
resp.start_line().set_status(bad_request);
|
|
resp.set_body(mime::text_plain, "Session id not specified");
|
|
return;
|
|
}
|
|
|
|
resp.headers().add(session_id_header, *it_session_id);
|
|
|
|
// Find the session to forward the request to
|
|
auto it_session = m_sessions.find(*it_session_id);
|
|
if (it_session == m_sessions.end())
|
|
{
|
|
resp.start_line().set_status(session_not_found);
|
|
return;
|
|
}
|
|
|
|
// Process the request in the session and complete populating the response
|
|
it_session->second.process_request(req, resp);
|
|
}
|
|
};
|
|
|
|
In the example above, the scope guards automatically perform their respective actions regardless of the return point of the
|
|
`process_request` method. `success_guard` makes sure the response is sent, whether it is a successful response after handling
|
|
the request in the target session object, or one of the error responses. `failure_guard` handles the case when the processing fails
|
|
with an exception and terminates the connection in this case. Note that as `failure_guard` is destroyed after `success_guard`,
|
|
it also covers errors that might occur while sending the response. Finally, `time_guard` logs the time it took to process the
|
|
request, whether successfully or with an exception, for diagnostic purposes.
|
|
|
|
Each of the scope guards described in this section supports active and inactive states. The action function object will only be
|
|
called if the scope guard is in active state while being destroyed. By default, scope guards are created in the active state, but
|
|
this can be changed by passing `false` as the last argument for the constructor. Scope guards can also be deactivated or
|
|
activated during their lifetime by calling the `set_active` method. Deactivating the scope guard is often used as a way to
|
|
"commit" the effects of the block of code between the scope guard creation and deactivation. Manual activation of a scope guard
|
|
can sometimes be useful if the scope guard needs to be created before it becomes known whether the scope guard action needs
|
|
to be executed (e.g. when the scope guard needs to be created at an outer scope compared to where the decision on whether it should
|
|
be enabled is made).
|
|
|
|
class collection
|
|
{
|
|
std::map< int, object > objects;
|
|
|
|
public:
|
|
// Finds an existing object by key or creates a new one and returns a reference to it
|
|
object& obtain_object(int key)
|
|
{
|
|
// Find the object or the position where it should be
|
|
auto it = objects.lower_bound(key);
|
|
|
|
// Create an inactive scope guard initially. Needs to be created
|
|
// at this scope to cover the on_requested() call below.
|
|
boost::scope::scope_fail rollback_guard{[&]
|
|
{
|
|
// Remove the object that was just inserted
|
|
objects.erase(it);
|
|
},
|
|
false};
|
|
|
|
if (it == objects.end() || it->first != key)
|
|
{
|
|
// Insert a new object that correspond to the specified key
|
|
it = objects.emplace_hint(it, key, object{});
|
|
|
|
// Activate rollback guard to revert the insertion in case of error
|
|
rollback_guard.set_active(true);
|
|
|
|
// Initialize the inserted object. May throw.
|
|
it->second.initialize(*this, key);
|
|
}
|
|
|
|
// Notify the object that is has been requested. May throw.
|
|
it->second.on_requested();
|
|
|
|
return it->second;
|
|
}
|
|
};
|
|
|
|
The code samples above rely on C++17 [@https://en.cppreference.com/w/cpp/language/class_template_argument_deduction class template
|
|
argument deduction (CTAD)] for the scope guard types to deduce the function object type (which is the lambda). If this feature is not
|
|
available, the scope guard construction can be rewritten using a factory function, like this:
|
|
|
|
auto rollback_guard = boost::scope::make_scope_fail([&]
|
|
{
|
|
objects.erase(it);
|
|
},
|
|
false);
|
|
|
|
Factory functions are provided for each of the three scope guards described in this section and are compatible with C++11. The factory
|
|
functions are named as `make_<scope_guard>` and accept the same arguments as the corresponding scope guard's constructor.
|
|
|
|
Scope guards described in this section are move-constructible (but not assignable), which requires the wrapped function objects to be
|
|
move- or copy-constructible as well. After moving, the moved-from scope guard becomes inactive. If a moved-from scope guard is active
|
|
on destruction, the behavior is undefined.
|
|
|
|
[endsect]
|
|
|
|
[section:condition_functions Scope guard condition functions]
|
|
|
|
#include <``[boost_scope_exception_checker_hpp]``>
|
|
#include <``[boost_scope_error_code_checker_hpp]``>
|
|
|
|
As discussed before, [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail] support specifying an additional
|
|
condition function object that will be called to decide whether the scope guard should invoke the action. A condition function object must
|
|
satisfy the following requirements:
|
|
|
|
* the type of a condition function object must be one of:
|
|
* a user-defined class with a public `operator()` taking no arguments, or
|
|
* an lvalue reference to such class, or
|
|
* an lvalue reference or pointer to a function taking no arguments;
|
|
* invoking the condition function object must return a value contextually convertible to `bool`, and
|
|
* invoking the condition function object must not throw exceptions.
|
|
|
|
For all the three scope guards, the condition function object is optional. If not specified, [class_scope_scope_exit] operates as if the
|
|
condition function object always returns `true` and will invoke its action function object as long as the scope guard is active. Otherwise,
|
|
the scope guard only invokes its action if the condition returns `true`.
|
|
|
|
For [class_scope_scope_success] and [class_scope_scope_fail], the [class_scope_exception_checker] condition function is used by default. It
|
|
works by capturing the number of uncaught exceptions on construction (which happens at the point of construction of the scope guard) and then
|
|
comparing the captured value with the number of uncaught exceptions when it is called (which happens when the scope guard is destroyed).
|
|
If the number has increased, it is taken as a sign that a new exception is in flight, and the predicate returns `true`; otherwise, the
|
|
predicate returns `false`.
|
|
|
|
[note By design, [class_scope_exception_checker] is intended for a specific use case with scope guards created on the stack. It is incompatible
|
|
with C++20 coroutines and similar facilities (e.g. fibers and userspace context switching), where the thread of execution may be suspended after
|
|
the predicate captures the number of uncaught exceptions and then resumed in a different context, where the number of uncaught exceptions
|
|
has changed. Similarly, it is incompatible with usage patterns where the predicate is cached after construction and is invoked after the
|
|
thread has left the scope where the predicate was constructed (e.g. when the predicate or the associated scope guard is stored as a class data
|
|
member or a namespace-scope variable).]
|
|
|
|
[class_scope_scope_success] invokes its action when the condition returns `false` (i.e. when the failure is not detected) and
|
|
[class_scope_scope_fail] - when the condition returns `true` (i.e. when the failure is detected). In relation to these two scope guard types,
|
|
the condition function object is sometimes called the failure condition because it indicates whether a failure has happened.
|
|
|
|
You may notice that [class_scope_scope_exit] behavior (with a user-specified condition function object) is similar to [class_scope_scope_fail]
|
|
and opposite to [class_scope_scope_success] in terms of their usage of condition function objects. The main difference is a semantic one:
|
|
[class_scope_scope_exit] does not have a success/failure connotation and may be used with arbitrary condition functions. On the other hand,
|
|
[class_scope_scope_success] and [class_scope_scope_fail] typically correspond to their respective intended use cases, and the default condition
|
|
function makes them equivalent to the scope guards defined in [@https://cplusplus.github.io/fundamentals-ts/v3.html#scope.syn
|
|
`<experimental/scope>`].
|
|
|
|
It is possible to emulate each of the scope guards described in this section by using [class_scope_scope_exit], either with a custom condition
|
|
function object, with the condition function embedded into the action function, or by activating or deactivating the scope guard after
|
|
construction. For example, the following four pieces of code have the same effect:
|
|
|
|
[table Comparison of `scope_fail` and `scope_exit`
|
|
[[`scope_fail`][`scope_exit` with a condition][`scope_exit` with embedded condition][`scope_exit` with manual deactivation]]
|
|
[[
|
|
```
|
|
void push_back(int x, std::vector< int >& vec1,
|
|
std::vector< int >& vec2)
|
|
{
|
|
vec1.push_back(x);
|
|
|
|
// Revert vec1 modification on failure
|
|
boost::scope::scope_fail rollback_guard{[&]
|
|
{
|
|
vec1.pop_back();
|
|
}};
|
|
|
|
vec2.push_back(x);
|
|
}
|
|
```
|
|
]
|
|
[
|
|
```
|
|
void push_back(int x, std::vector< int >& vec1,
|
|
std::vector< int >& vec2)
|
|
{
|
|
vec1.push_back(x);
|
|
|
|
// Revert vec1 modification on failure
|
|
boost::scope::scope_exit rollback_guard
|
|
{
|
|
[&] { vec1.pop_back(); },
|
|
boost::scope::exception_checker()
|
|
};
|
|
|
|
vec2.push_back(x);
|
|
}
|
|
```
|
|
]
|
|
[
|
|
```
|
|
void push_back(int x, std::vector< int >& vec1,
|
|
std::vector< int >& vec2)
|
|
{
|
|
vec1.push_back(x);
|
|
|
|
// Revert vec1 modification on failure
|
|
boost::scope::scope_exit rollback_guard
|
|
{
|
|
[&, uncaught_count = std::uncaught_exceptions()]
|
|
{
|
|
if (std::uncaught_exceptions() > uncaught_count)
|
|
vec1.pop_back();
|
|
}
|
|
};
|
|
|
|
vec2.push_back(x);
|
|
}
|
|
```
|
|
]
|
|
[
|
|
```
|
|
void push_back(int x, std::vector< int >& vec1,
|
|
std::vector< int >& vec2)
|
|
{
|
|
vec1.push_back(x);
|
|
|
|
// Revert vec1 modification on failure
|
|
boost::scope::scope_exit rollback_guard{[&]
|
|
{
|
|
vec1.pop_back();
|
|
}};
|
|
|
|
vec2.push_back(x);
|
|
|
|
// Commit vec1 modification
|
|
rollback_guard.set_active(false);
|
|
}
|
|
```
|
|
]]
|
|
]
|
|
|
|
The main benefit of using [class_scope_scope_fail] in this example is reducing code size and complexity. You can see that adding the check
|
|
for the number of uncaught exceptions to the scope guard action makes it notably more verbose, and if such a check needs to be performed
|
|
in multiple scope guards, this code would have to be duplicated. Explicitly deactivating the scope guard also has its downsides, as it may
|
|
be prone to errors if it has to be performed in multiple places of the code (for example, when there are multiple return points, where
|
|
the transaction needs to be committed). This goes against the intended purpose of the scope guard, as it was supposed to automate the correct
|
|
execution of its action without the user having to manually ensure this at every possible point of leaving the scope.
|
|
|
|
Another benefit of [class_scope_scope_fail] is improved code readability. For a reader, [class_scope_scope_fail] immediately indicates
|
|
an error handler, whereas [class_scope_scope_exit] does not have such connotation and may contain a general cleanup code.
|
|
|
|
That being said, there may be reasons to still use one of the techniques demonstrated above instead of
|
|
[class_scope_scope_success]/[class_scope_scope_fail], especially when paired with [class_scope_exception_checker]. As noted above,
|
|
[class_scope_exception_checker] is incompatible with coroutines and similar facilities, so using a manually managed [class_scope_scope_exit]
|
|
can be a solution. Another consideration is performance, specifically in relation with [class_scope_exception_checker]. This predicate has
|
|
to invoke runtime functions to obtain the number of uncaught exceptions, and has to do this twice - on scope guard construction and destruction.
|
|
Although these functions are usually cheap, these calls are typically not optimized away by the compiler, even if no exception is thrown,
|
|
and deactivating a scope guard is cheaper still. So, if a scope guard is used in a tight loop where its performance overhead may be significant,
|
|
preferring [class_scope_scope_exit] with manual deactivation may be a reasonable choice.
|
|
|
|
[class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail] accept the condition function object as the second argument
|
|
for the constructor or factory function, after the action function object. If the condition function object is default-constructible and
|
|
the default constructor doesn't throw, the function object may be omitted from the scope guard constructor arguments.
|
|
|
|
// Writes a string to a file using a file descriptor, with file locking.
|
|
// Reports errors via an error_code parameter.
|
|
void locked_write_string(int fd, std::string const& str, std::error_code& ec)
|
|
{
|
|
int err = 0;
|
|
|
|
// Automatically transform errors to error_code on exit
|
|
boost::scope::scope_fail fail_guard
|
|
{
|
|
// Action function object
|
|
[&] { ec = std::error_code(err, std::generic_category()); },
|
|
// Condition function object
|
|
[&err]() noexcept { return err != 0; }
|
|
};
|
|
|
|
// Performs a lock operation on the file descriptor, returns error code or 0 on success
|
|
auto lock_operation = [fd](int operation)
|
|
{
|
|
while (flock(fd, operation) < 0)
|
|
{
|
|
int err = errno;
|
|
if (err != EINTR)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
// Lock the file
|
|
err = lock_operation(LOCK_EX);
|
|
if (err != 0)
|
|
return;
|
|
|
|
// Unlock the file on exit
|
|
BOOST_SCOPE_DEFER [&] { lock_operation(LOCK_UN); };
|
|
|
|
// Write data
|
|
const char* p = str.data();
|
|
std::size_t size_left = str.size();
|
|
while (size_left > 0)
|
|
{
|
|
ssize_t written = write(fd, p, size_left);
|
|
if (written < 0)
|
|
{
|
|
err = errno;
|
|
if (err == EINTR)
|
|
{
|
|
err = 0; // we do not want to report EINTR to the caller
|
|
continue;
|
|
}
|
|
return;
|
|
}
|
|
|
|
p += written;
|
|
size_left -= written;
|
|
}
|
|
}
|
|
|
|
Besides [class_scope_exception_checker], the library also provides [class_scope_error_code_checker] that can be used as a condition
|
|
function object. On construction, [class_scope_error_code_checker] captures a reference to an external error code object and checks it
|
|
for an error indication when being called. This implies that the error code object must remain valid for the entire lifetime duration of
|
|
the [class_scope_error_code_checker] predicate.
|
|
|
|
An object `ec` can be used as an error code object if:
|
|
|
|
* the expression `!ec` is valid, never throws, and returns a value contextually convertible to `bool`, and
|
|
* the expression `!ec` produces a value contextually convertible to `true` when there is no error and `false` otherwise.
|
|
|
|
That is, for an error code object `ec`, invoking [class_scope_error_code_checker] results in a value equivalent to `!!ec`. This makes
|
|
[class_scope_error_code_checker] compatible with a wide variety of error code types, including:
|
|
|
|
* `std::error_code` or `boost::system::error_code` from __boost_system__,
|
|
* `std::expected`, `boost::outcome_v2::basic_outcome` or `boost::outcome_v2::basic_result` from __boost_outcome__,
|
|
* `int`, where the value of 0 indicates no error,
|
|
* `bool`, where the value of `false` indicates no error,
|
|
* `T*`, where a null pointer indicates no error.
|
|
|
|
For C++11 compilers, the library also provides a factory function `check_error_code`. For example, our previous example could use
|
|
this condition function object like this:
|
|
|
|
// Writes a string to a file using a file descriptor, with file locking.
|
|
// Reports errors via an error_code parameter.
|
|
void locked_write_string(int fd, std::string const& str, std::error_code& ec)
|
|
{
|
|
int err = 0;
|
|
|
|
// Automatically transform errors to error_code on exit
|
|
boost::scope::scope_fail fail_guard
|
|
{
|
|
// Action function object
|
|
[&] { ec = std::error_code(err, std::generic_category()); },
|
|
// Condition function object
|
|
boost::scope::check_error_code(err)
|
|
};
|
|
|
|
// ...
|
|
}
|
|
|
|
[endsect]
|
|
|
|
[section:unconditional Unconditional scope guard: `defer_guard`]
|
|
|
|
#include <``[boost_scope_defer_hpp]``>
|
|
|
|
The [class_scope_defer_guard] scope guard is similar to [class_scope_scope_exit] without a user-specified condition function object in
|
|
terms of when it invokes the action function. But it does not support custom condition functions and lacks support for moveability and
|
|
activation/deactivation - this scope guard is always active upon construction. This allows for a more efficient implementation when
|
|
these features are not needed.
|
|
|
|
[note [class_scope_defer_guard] is a more lightweight version of [class_scope_scope_exit], similar to how `std::lock_guard` is a more
|
|
lightweight version of `std::unique_lock`.]
|
|
|
|
Since [class_scope_defer_guard] effectively provides no interface to interact with after construction, it is better suited for anonymous
|
|
"set up and forget" kind of scope guards. To emphasize this affinity, the library provides a `BOOST_SCOPE_DEFER` macro, which acts as
|
|
a keyword defining a uniquely named [class_scope_defer_guard] object. The macro should be followed by the function object to be invoked
|
|
on scope exit.
|
|
|
|
BOOST_SCOPE_DEFER []
|
|
{
|
|
std::cout << "Hello world!" << std::endl;
|
|
};
|
|
|
|
[note `BOOST_SCOPE_DEFER` requires support for C++17 [@https://en.cppreference.com/w/cpp/language/class_template_argument_deduction CTAD].
|
|
The [class_scope_defer_guard] class itself is compatible with C++11, but given that there is no factory function for it, C++17 support is
|
|
very much desired.]
|
|
|
|
As you can see, `BOOST_SCOPE_DEFER` offers a few syntax improvements over the other scope guard declarations:
|
|
|
|
* The declaration does not name a scope guard variable, meaning one does not need to invent one and there is no possibility to accidentally
|
|
omit one or clash with other variables.
|
|
* The declaration is generally shorter to type and easier to spot.
|
|
* There are no extra parenthesis or curly braces around the function object.
|
|
|
|
However, it should be noted that the use of the `BOOST_SCOPE_DEFER` macro is entirely optional. Users are free to use [class_scope_defer_guard]
|
|
directly.
|
|
|
|
boost::scope::defer_guard guard{[]
|
|
{
|
|
std::cout << "Hello world!" << std::endl;
|
|
}};
|
|
|
|
[endsect]
|
|
|
|
[section:capture_by_reference_caveats Caveats of capturing by reference]
|
|
|
|
When using scope guards, users should make sure that all variables captured by reference are still in a valid state upon the scope guard
|
|
destruction. This especially pertains to lambda functions that capture variables by reference by default (i.e. where the capture clause
|
|
starts with `&`). Consider the following example:
|
|
|
|
void bad(std::unique_ptr< int > ptr)
|
|
{
|
|
assert(ptr != nullptr);
|
|
|
|
boost::scope::scope_exit guard{[&]
|
|
{
|
|
std::cout << "processed: " << *ptr << std::endl;
|
|
}};
|
|
|
|
process(std::move(ptr)); // may consume ptr, leaving it null for the scope guard action
|
|
}
|
|
|
|
Or a slightly less obvious version of it, involving implicit move of the returned value:
|
|
|
|
std::unique_ptr< int > bad(std::unique_ptr< int > ptr)
|
|
{
|
|
assert(ptr != nullptr);
|
|
|
|
boost::scope::scope_exit guard{[&]
|
|
{
|
|
std::cout << "processed: " << *ptr << std::endl;
|
|
}};
|
|
|
|
process(*ptr);
|
|
|
|
return ptr; // moves from ptr, leaving it null for the scope guard action
|
|
}
|
|
|
|
In cases like this consider capturing by value in the function object or moving the scope guard to a deeper scope, so that it executes
|
|
its action before the variables captured by reference become invalid.
|
|
|
|
std::unique_ptr< int > good(std::unique_ptr< int > ptr)
|
|
{
|
|
assert(ptr != nullptr);
|
|
|
|
{
|
|
boost::scope::scope_exit guard{[&]
|
|
{
|
|
std::cout << "processed: " << *ptr << std::endl;
|
|
}};
|
|
|
|
process(*ptr);
|
|
} // <- scope guard action runs here, before ptr is moved-from
|
|
|
|
return ptr;
|
|
}
|
|
|
|
[endsect]
|
|
|
|
[section:runtime_defined Setting up scope exit actions at run time]
|
|
|
|
It is possible to use scope guard classes to implement scope exit actions that are initialized at run time. One way to do this is to use
|
|
a function object wrapper such as `std::function` together with the scope guard to schedule the function call. For example:
|
|
|
|
using cleanup_func_t = std::function< void() >;
|
|
cleanup_func_t cleanup_func;
|
|
// Create an inactive scope guard first, since the cleanup function is not set yet
|
|
boost::scope::scope_exit< cleanup_func_t& > cleanup(cleanup_func, false);
|
|
|
|
// Later in the program, initialize the cleanup function with the function selected at run time
|
|
if (cond)
|
|
{
|
|
cleanup_func = []
|
|
{
|
|
std::cout << "cond is true" << std::endl;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
cleanup_func = []
|
|
{
|
|
std::cout << "cond is false" << std::endl;
|
|
};
|
|
}
|
|
|
|
// Activate the scope guard once the cleanup function is initialized
|
|
cleanup.set_active(true);
|
|
|
|
It is also possible to do this with `BOOST_SCOPE_DEFER`, although it eliminates one of the advantages provided by this macro, namely not
|
|
having to invent a variable name. Also note that the function wrapper must be valid at all times once the scope guard is constructed.
|
|
|
|
// Create a non-empty function wrapper that does nothing
|
|
std::function< void() > cleanup_func = [] {};
|
|
// Create a scope guard that refers to the function wrapper
|
|
BOOST_SCOPE_DEFER std::ref(cleanup_func);
|
|
|
|
// Later in the program, initialize the function wrapper
|
|
if (cond)
|
|
{
|
|
cleanup_func = []
|
|
{
|
|
std::cout << "cond is true" << std::endl;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
cleanup_func = []
|
|
{
|
|
std::cout << "cond is false" << std::endl;
|
|
};
|
|
}
|
|
|
|
However, users should be aware that function wrappers typically use dynamic memory allocation internally and copy the function object data,
|
|
which may involve calling copy constructors that may also fail with an exception. Although many standard library implementations use small
|
|
object optimization for `std::function`, and this technique is also used in other implementations like __boost_function__, it is generally
|
|
not guaranteed that initializing the function wrapper with a given function object will not throw. Because of this, using `std::function`
|
|
and similar wrappers is usually not recommended.
|
|
|
|
If setting up the scope exit action needs to be a non-throwing operation (for example, if the scope guard is supposed to revert the effects
|
|
of the immediately preceding operation), it is recommended to initialize inactive scope guards beforehand and only activate one of them at
|
|
a later point in the program.
|
|
|
|
// Create inactive scope guards for both branches
|
|
boost::scope::scope_exit cleanup_true([]
|
|
{
|
|
std::cout << "cond is true" << std::endl;
|
|
},
|
|
false);
|
|
boost::scope::scope_exit cleanup_false([]
|
|
{
|
|
std::cout << "cond is false" << std::endl;
|
|
},
|
|
false);
|
|
|
|
// Later in the program, activate one of the scope guards.
|
|
// This won't throw.
|
|
if (cond)
|
|
cleanup_true.set_active(true);
|
|
else
|
|
cleanup_false.set_active(true);
|
|
|
|
Alternatively, one could implement the selection within the scope guard action itself.
|
|
|
|
// Create a single inactive scope guard that implements both branches
|
|
bool cond;
|
|
boost::scope::scope_exit cleanup([&cond]
|
|
{
|
|
if (cond)
|
|
std::cout << "cond is true" << std::endl;
|
|
else
|
|
std::cout << "cond is false" << std::endl;
|
|
},
|
|
false);
|
|
|
|
// Later in the program, select the branch to perform
|
|
// and activate the scope guard.
|
|
cond = select_branch();
|
|
cleanup.set_active(true);
|
|
|
|
[endsect]
|
|
|
|
[section:comparison_with_boost_scope_exit Comparison with Boost.ScopeExit library]
|
|
|
|
__boost_scope_exit__ defines a set of macros for defining code blocks to be executed at scope exit. Scope guards provided by Boost.Scope
|
|
provide similar functionality, but with simpler syntax and new features. Differences between libraries are summarized in the table below.
|
|
|
|
[table __boost_scope_exit__ and Boost.Scope comparison
|
|
[[Feature] [__boost_scope_exit__] [Boost.Scope]]
|
|
[[
|
|
Minimum and recommended C++ version.
|
|
]
|
|
[
|
|
C++03 minimum, C++11 recommended for some features.
|
|
]
|
|
[
|
|
C++11 minimum, C++17 recommended for some features.
|
|
]]
|
|
[[
|
|
Basic functionality.
|
|
]
|
|
[
|
|
```
|
|
BOOST_SCOPE_EXIT(void)
|
|
{
|
|
std::cout << "Hello, World!" << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
```
|
|
]
|
|
[
|
|
```
|
|
boost::scope::scope_exit guard([]
|
|
{
|
|
std::cout << "Hello, World!" << std::endl;
|
|
});
|
|
```
|
|
|
|
Or:
|
|
|
|
```
|
|
BOOST_SCOPE_DEFER []
|
|
{
|
|
std::cout << "Hello, World!" << std::endl;
|
|
};
|
|
```
|
|
]]
|
|
[[
|
|
Capturing variables.
|
|
]
|
|
[
|
|
Yes, both by value and by reference.
|
|
|
|
```
|
|
int x;
|
|
std::string str;
|
|
BOOST_SCOPE_EXIT(x, &str)
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
```
|
|
|
|
In C++03 mode without variadic macros support, Boost.Preprocessor sequence should be used to list captured variables.
|
|
]
|
|
[
|
|
Yes, by means of the standard C++ lambda captures.
|
|
|
|
```
|
|
int x;
|
|
std::string str;
|
|
boost::scope::scope_exit guard([x, &str]
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
});
|
|
```
|
|
|
|
Or:
|
|
|
|
```
|
|
int x;
|
|
std::string str;
|
|
BOOST_SCOPE_DEFER [x, &str]
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
};
|
|
```
|
|
]]
|
|
[[
|
|
Capturing all variables.
|
|
]
|
|
[
|
|
Yes, requires C++11.
|
|
|
|
```
|
|
int x;
|
|
std::string str;
|
|
BOOST_SCOPE_EXIT_ALL(&)
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
};
|
|
```
|
|
|
|
Note that no `BOOST_SCOPE_EXIT_END` is used in this case. See below for `BOOST_SCOPE_EXIT_ALL` caveats.
|
|
]
|
|
[
|
|
Yes, by means of the standard C++ lambda captures.
|
|
|
|
```
|
|
int x;
|
|
std::string str;
|
|
boost::scope::scope_exit guard([&]
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
});
|
|
```
|
|
|
|
Or:
|
|
|
|
```
|
|
int x;
|
|
std::string str;
|
|
BOOST_SCOPE_DEFER [&]
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
};
|
|
```
|
|
]]
|
|
[[
|
|
Capturing `this`.
|
|
]
|
|
[
|
|
Yes, with a special `this_` keyword.
|
|
|
|
```
|
|
class my_class
|
|
{
|
|
private:
|
|
int x;
|
|
std::string str;
|
|
|
|
public:
|
|
void foo()
|
|
{
|
|
BOOST_SCOPE_EXIT(this_)
|
|
{
|
|
std::cout << "x = " << this_->x << ", str = " << this_->str << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
}
|
|
};
|
|
```
|
|
|
|
Note that the keyword must be explicitly used to reference members of the class within the scope exit block.
|
|
|
|
Capturing `this` is also possible using `BOOST_SCOPE_EXIT_ALL` (with the caveats described below).
|
|
|
|
```
|
|
class my_class
|
|
{
|
|
private:
|
|
int x;
|
|
std::string str;
|
|
|
|
public:
|
|
void foo()
|
|
{
|
|
BOOST_SCOPE_EXIT_ALL(this)
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
};
|
|
}
|
|
};
|
|
```
|
|
]
|
|
[
|
|
Yes, by means of the standard C++ lambda captures.
|
|
|
|
```
|
|
class my_class
|
|
{
|
|
private:
|
|
int x;
|
|
std::string str;
|
|
|
|
public:
|
|
void foo()
|
|
{
|
|
boost::scope::scope_exit guard([this]
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
};
|
|
}
|
|
};
|
|
```
|
|
|
|
Or:
|
|
|
|
```
|
|
class my_class
|
|
{
|
|
private:
|
|
int x;
|
|
std::string str;
|
|
|
|
public:
|
|
void foo()
|
|
{
|
|
BOOST_SCOPE_DEFER [this]
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
};
|
|
}
|
|
};
|
|
```
|
|
]]
|
|
[[
|
|
Capturing specific members of a class.
|
|
]
|
|
[
|
|
Yes.
|
|
|
|
```
|
|
class my_class
|
|
{
|
|
private:
|
|
int x;
|
|
std::string str;
|
|
|
|
public:
|
|
void foo()
|
|
{
|
|
BOOST_SCOPE_EXIT(x, &str)
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
}
|
|
};
|
|
```
|
|
]
|
|
[
|
|
No. Capture `this` or use lambda capture initializers (requires C++14) instead.
|
|
|
|
```
|
|
class my_class
|
|
{
|
|
private:
|
|
int x;
|
|
std::string str;
|
|
|
|
public:
|
|
void foo()
|
|
{
|
|
BOOST_SCOPE_DEFER [x = x, str = std::ref(str)]
|
|
{
|
|
std::cout << "x = " << x << ", str = " << str.get() << std::endl;
|
|
};
|
|
}
|
|
};
|
|
```
|
|
]]
|
|
[[
|
|
Support for scope guards in templates.
|
|
]
|
|
[
|
|
Yes, using a special macro.
|
|
|
|
```
|
|
template< typename T >
|
|
void foo(T const& param)
|
|
{
|
|
BOOST_SCOPE_EXIT_TPL(¶m)
|
|
{
|
|
std::cout << "Param: " << param << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
}
|
|
```
|
|
]
|
|
[
|
|
Yes, with no special syntax.
|
|
|
|
```
|
|
template< typename T >
|
|
void foo(T const& param)
|
|
{
|
|
BOOST_SCOPE_DEFER [¶m]
|
|
{
|
|
std::cout << "Param: " << param << std::endl;
|
|
};
|
|
}
|
|
```
|
|
]]
|
|
[[
|
|
Support for variadic templates and argument packs.
|
|
]
|
|
[
|
|
Yes, using the `BOOST_SCOPE_EXIT_ALL` macro (see the caveats below).
|
|
|
|
```
|
|
template< typename... Args >
|
|
void foo(Args&&... args)
|
|
{
|
|
BOOST_SCOPE_EXIT_ALL(&args...)
|
|
{
|
|
std::cout << "Params: " << boost::tuples::tie(args...) << std::endl;
|
|
};
|
|
}
|
|
```
|
|
]
|
|
[
|
|
Yes, by means of the standard C++ lambda captures.
|
|
|
|
```
|
|
template< typename... Args >
|
|
void foo(Args&&... args)
|
|
{
|
|
BOOST_SCOPE_DEFER [&args...]
|
|
{
|
|
std::cout << "Params: " << boost::tuples::tie(args...) << std::endl;
|
|
};
|
|
}
|
|
```
|
|
]]
|
|
[[
|
|
Native support for activation/deactivation of the scope guard.
|
|
]
|
|
[
|
|
No, the activation/deactivation logic needs to be embedded into the action code block.
|
|
|
|
```
|
|
bool active = false;
|
|
BOOST_SCOPE_EXIT(&active)
|
|
{
|
|
if (active)
|
|
std::cout << "Hello, World!" << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
|
|
active = true;
|
|
```
|
|
]
|
|
[
|
|
Yes, with [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail]. This includes the ability to delay activation
|
|
of the initially inactive scope guards.
|
|
|
|
```
|
|
boost::scope::scope_exit guard([]
|
|
{
|
|
std::cout << "Hello, World!" << std::endl;
|
|
},
|
|
false);
|
|
|
|
guard.set_active(true);
|
|
```
|
|
]]
|
|
[[
|
|
Support for custom conditions for executing the action.
|
|
]
|
|
[
|
|
No, the condition needs to be embedded into the action code block.
|
|
|
|
```
|
|
const auto uncaught_count = std::uncaught_exceptions();
|
|
BOOST_SCOPE_EXIT(uncaught_count)
|
|
{
|
|
if (std::uncaught_exceptions() > uncaught_count)
|
|
std::cout << "Exception thrown!" << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
```
|
|
]
|
|
[
|
|
Yes, with [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail].
|
|
|
|
```
|
|
boost::scope::scope_exit guard(
|
|
[]
|
|
{
|
|
std::cout << "Exception thrown!" << std::endl;
|
|
},
|
|
[uncaught_count = std::uncaught_exceptions()]
|
|
{
|
|
return std::uncaught_exceptions() > uncaught_count;
|
|
});
|
|
```
|
|
|
|
Or simply:
|
|
|
|
```
|
|
boost::scope::scope_fail guard([]
|
|
{
|
|
std::cout << "Exception thrown!" << std::endl;
|
|
});
|
|
```
|
|
]]
|
|
[[
|
|
Utilities for checking exceptions and error codes as criteria for invoking the action.
|
|
]
|
|
[
|
|
No, the condition needs to be embedded into the action code block.
|
|
|
|
```
|
|
const unsigned int uncaught_count = boost::core::uncaught_exceptions();
|
|
BOOST_SCOPE_EXIT(uncaught_count)
|
|
{
|
|
if (uncaught_count < boost::core::uncaught_exceptions())
|
|
std::cout << "Exception thrown!" << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
```
|
|
|
|
Or:
|
|
|
|
```
|
|
std::error_code ec;
|
|
BOOST_SCOPE_EXIT(&ec)
|
|
{
|
|
if (ec)
|
|
std::cout << "Execution failed!" << std::endl;
|
|
}
|
|
BOOST_SCOPE_EXIT_END;
|
|
```
|
|
]
|
|
[
|
|
Yes, [class_scope_exception_checker] and [class_scope_error_code_checker].
|
|
|
|
```
|
|
// scope_fail by default checks for an exception
|
|
boost::scope::scope_fail guard([]
|
|
{
|
|
std::cout << "Exception thrown!" << std::endl;
|
|
});
|
|
```
|
|
|
|
Or:
|
|
|
|
```
|
|
std::error_code ec;
|
|
boost::scope::scope_fail guard([]
|
|
{
|
|
std::cout << "Execution failed!" << std::endl;
|
|
},
|
|
boost::scope::check_error_code(ec));
|
|
```
|
|
]]
|
|
[[
|
|
Named scope guards.
|
|
]
|
|
[
|
|
No, the scope guard is declared as an unnamed code block. (The scope guard is internally implemented as an object with a unique name on
|
|
the stack, but this is not part of the public interface.)
|
|
]
|
|
[
|
|
Yes, scope guards are defined as objects. Also, [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail] are
|
|
move-constructible and can be returned from functions.
|
|
]]
|
|
[[
|
|
Anonymous scope guards.
|
|
]
|
|
[
|
|
Yes.
|
|
]
|
|
[
|
|
Yes, using the `BOOST_SCOPE_DEFER` macro.
|
|
]]
|
|
[[
|
|
Macro-less usage.
|
|
]
|
|
[
|
|
No.
|
|
]
|
|
[
|
|
Yes.
|
|
]]
|
|
]
|
|
|
|
In the table above, you may notice that a significant amount of feature parity between __boost_scope_exit__ and Boost.Scope is achieved by
|
|
using the `BOOST_SCOPE_EXIT_ALL` macro in the former. This macro is implemented using C++11 lambda functions internally, which explains
|
|
a lot of similarities between the libraries in this case. Users can also define `BOOST_SCOPE_EXIT_CONFIG_USE_LAMBDAS` to force other
|
|
__boost_scope_exit__ macros to use C++11 lambdas internally, which will change the syntax and variable capture rules accordingly. However,
|
|
there are a few caveats one needs to keep in mind when using the library in this mode (or the `BOOST_SCOPE_EXIT_ALL` macro in any mode):
|
|
|
|
* Since it relies on C++11 lambda functions, it is not available in C++03 mode.
|
|
* __boost_scope_exit__ scope guards that are implemented using C++11 lambdas store the lambda function in a `boost::function` object
|
|
internally. This means that creating the scope guard may require dynamic memory allocation and can fail with an exception. __boost_scope_exit__
|
|
does not document behavior in this case, but in practice, as of Boost 1.83.0, the scope exit block does not get executed in this case.
|
|
This means that __boost_scope_exit__ in this mode cannot be safely used in non-throwing contexts, and cannot guarantee execution of the
|
|
scope exit block. There is also performance overhead associated both with construction and execution of the scope guard.
|
|
|
|
In comparison, Boost.Scope scope guards do not use function object wrappers, such as `boost::function` or `std::function`, to store the
|
|
function objects. This means that the scope guards do not perform dynamic memory allocation (unless the function objects themselves do)
|
|
and do not have the associated exception safety issues and performance overhead. Additionally, the scope guards will execute the action
|
|
if initializing the scope guard fails with an exception.
|
|
|
|
[endsect]
|
|
|
|
[section:comparison_with_library_fundamentals_ts Comparison with scope guards defined in C++ Extensions for Library Fundamentals]
|
|
|
|
The [@https://cplusplus.github.io/fundamentals-ts/v3.html#scopeguard.exit C++ Extensions for Library Fundamentals TS] defines three scope
|
|
guard types, `scope_exit`, `scope_success` and `scope_fail`, which have similar semantics to the same-named scope guards provided by this
|
|
library. There are some differences and extensions that are summarised below.
|
|
|
|
[section:condition_functions Condition functions]
|
|
|
|
All three of the [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail] scope guards provided by Boost.Scope
|
|
support an optional [link scope.scope_guards.condition_functions condition function] that is used to decide whether the scope guard should
|
|
invoke its wrapped action function. The scope guards defined by the TS do not have this feature, the behavior of each of the three scope
|
|
guards is predefined by the specification.
|
|
|
|
The default condition functions used in the Boost.Scope scope guards make the behavior equivalent to the TS, so if the user doesn't use
|
|
custom condition functions, the scope guard usage is equivalent between the two. If custom condition functions are used, those can be
|
|
emulated in TS scope guards by embedding the condition function into the action called by `std::experimental::scope_exit`. For example,
|
|
if the user's code uses error codes as the failure indication with [class_scope_scope_fail], the equivalent TS code could look like this:
|
|
|
|
[table Using custom condition functions
|
|
[[Boost.Scope][Library Fundamentals TS]]
|
|
[[
|
|
```
|
|
std::error_code ec;
|
|
boost::scope::scope_fail guard
|
|
{
|
|
[] { handle_error(); },
|
|
boost::scope::check_error_code(ec)
|
|
};
|
|
```
|
|
]
|
|
[
|
|
```
|
|
std::error_code ec;
|
|
std::experimental::scope_exit guard
|
|
{
|
|
[&ec]
|
|
{
|
|
if (ec)
|
|
handle_error();
|
|
}
|
|
};
|
|
```
|
|
]]
|
|
]
|
|
|
|
Note that we had to switch from `scope_fail` to `scope_exit` in case of TS.
|
|
|
|
[endsect]
|
|
|
|
[section:activation Flexible activation and deactivation of scope guards]
|
|
|
|
Scope guards defined in the TS are always constructed active and can only be deactivated by calling `release()` method. Boost.Scope
|
|
supports creating initially inactive scope guards, as well as activating and deactivating them after creation. This allows for more
|
|
flexible placement of the scope guards in the user's code, which is useful if the decision on whether to execute the scope guard action
|
|
needs to be made after the scope guard is constructed.
|
|
|
|
This feature can be emulated in TS using additional state that the scope guard action checks to see if the action needs to be performed.
|
|
|
|
[table Explicitly activating scope guards
|
|
[[Boost.Scope][Library Fundamentals TS]]
|
|
[[
|
|
```
|
|
// Insert value into both containers and call on_inserted() afterwards.
|
|
// pv may be null, in which case only insert the value into the set.
|
|
// Maintain both containers intact if an exception is thrown.
|
|
void insert_two(std::vector< int >* pv, std::set< int >& set, int value)
|
|
{
|
|
// Initially inactive scope guard
|
|
boost::scope::scope_fail pv_rollback([pv] { pv->pop_back(); }, false);
|
|
if (pv)
|
|
{
|
|
pv->push_back(value); // may throw
|
|
pv_rollback.set_active(true); // activate the scope guard
|
|
}
|
|
|
|
auto [it, inserted] = set.insert(value); // may throw
|
|
// Initially active scope guard, if a new element was inserted, otherwise inactive
|
|
boost::scope::scope_fail set_rollback([&set, &it] { set.erase(it); }, inserted);
|
|
|
|
on_inserted(); // may throw
|
|
}
|
|
```
|
|
]
|
|
[
|
|
```
|
|
// Insert value into both containers and call on_inserted() afterwards.
|
|
// pv may be null, in which case only insert the value into the set.
|
|
// Maintain both containers intact if an exception is thrown.
|
|
void insert_two(std::vector< int >* pv, std::set< int >& set, int value)
|
|
{
|
|
bool pv_inserted = false;
|
|
// Initially active scope guard, but blocked by pv_inserted == false
|
|
std::experimental::scope_exit pv_rollback([pv, &pv_inserted]
|
|
{
|
|
if (pv_inserted)
|
|
pv->pop_back();
|
|
});
|
|
if (pv)
|
|
{
|
|
pv->push_back(value); // may throw
|
|
pv_inserted = true; // unblock the scope guard
|
|
}
|
|
|
|
auto [it, inserted] = set.insert(value); // may throw
|
|
// Initially active scope guard, which we immediately deactivate if no element was inserted
|
|
std::experimental::scope_exit set_rollback([&set, &it] { set.erase(it); });
|
|
if (!inserted)
|
|
set_rollback.release();
|
|
|
|
on_inserted(); // may throw
|
|
}
|
|
```
|
|
]]
|
|
]
|
|
|
|
Note that Boost.Scope guards don't provide `release()` member function and instead provide `set_active()` with a `bool` argument,
|
|
where `release()` is equivalent to `set_active(false)`. This allows for both activating and deactivating the scope guard. The `bool`
|
|
argument is more convenient than two distinct methods for activating and deactivating the scope guard when the indication of whether
|
|
the scope guard needs to be active is expressed as a run time value. For example, the insertion into `set` above could be written
|
|
like this:
|
|
|
|
std::set< int >::iterator it;
|
|
// Initially create an inactive scope guard to revert the insertion
|
|
boost::scope::scope_fail set_rollback([&set, &it] { set.erase(it); }, false);
|
|
|
|
// Try insering a new element into the set
|
|
bool inserted;
|
|
std::tie(it, inserted) = set.insert(value); // may throw
|
|
|
|
// Activate the scope guard only if the element was inserted
|
|
set_rollback.set_active(inserted);
|
|
|
|
[note At least one person during the Boost.Scope review [@https://lists.boost.org/Archives/boost/2023/11/255424.php noted] that
|
|
`release()` is a poor name in the context of scope guards. This, and the fact that a method with a `bool` argument is more flexible
|
|
and provides the same functionality, is why Boost.Scope does not provide `release()`.]
|
|
|
|
In addition to `set_active()`, Boost.Scope scope guards can be queried whether they are active via the `active()` method. This feature
|
|
has no equivalent in the TS.
|
|
|
|
[endsect]
|
|
|
|
[section:relaxed_dtor_noexcept Relaxed `noexcept` specification on destructors]
|
|
|
|
In the Library Fundamentals TS, only `scope_success` action is allowed to throw exceptions, while `scope_fail` and `scope_exit`
|
|
destructors are unconditionally marked `noexcept`. Since the TS does not support custom condition functions, `scope_fail` can only
|
|
invoke its action while an exception is unwinding the stack, so throwing an exception from the scope guard action would cause the
|
|
program to terminate. In case of `scope_exit` the justification is less definitive, as this scope guard may invoke its action when
|
|
no exception is in flight. However, the situation when the action is called due to an exception is still possible, and the TS takes
|
|
a conservative approach by prohibiting the action to throw exceptions.
|
|
|
|
Boost.Scope takes a different approach. Since all three scope guards support custom condition functions, each of them may invoke
|
|
its action whether or not an exception is propagating through the stack. The notion of "failure" is no longer equivalent to "exception",
|
|
so even for a [class_scope_scope_fail] scope guard with a custom failure condition it may make sense to allow throwing exceptions.
|
|
Therefore, for all scope guards provided by Boost.Scope, their destructors are `noexcept` only if their action and condition
|
|
functions are both marked `noexcept`. It is user's responsibity to ensure that the scope guard actions and conditions do not
|
|
throw exceptions when another exception is in flight. The user may even perform a runtime check with `std::uncaught_exceptions`, if
|
|
needed. If the user is willing to enforce the non-throwing guarantee for his scope guards, he is able to do this by marking his action
|
|
functions `noexcept`:
|
|
|
|
boost::scope::scope_exit guard([&]() noexcept { /* I promise I won't throw */ });
|
|
|
|
While this allows more scope guards to throw exceptions, it makes no difference in behavior when the action throws during an exception
|
|
propagation. Whether the scope guard's destructor is marked `noexcept` or not, the program will terminate either way in this case.
|
|
|
|
This Boost.Scope feature cannot be emulated with TS scope guards (other than when the use case fits `scope_success`).
|
|
|
|
[endsect]
|
|
|
|
[endsect]
|
|
|
|
[endsect]
|