[/ / 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_` 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 ``]. 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] scope guard. 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() >; // Create an inactive scope guard first, since the cleanup function is not set yet boost::scope::scope_exit< cleanup_func_t > cleanup(cleanup_func_t(), false); // Later in the program, initialize the scope guard with the function selected at run time if (cond) { cleanup = boost::scope::scope_exit< cleanup_func_t >([] { std::cout << "cond is true" << std::endl; }); } else { cleanup = boost::scope::scope_exit< cleanup_func_t >([] { std::cout << "cond is false" << std::endl; }); } 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]