From 56afb11dbd138eea79cc962bcc747c5b948930c0 Mon Sep 17 00:00:00 2001 From: Andrey Semashev Date: Sun, 28 Jan 2024 04:11:45 +0300 Subject: [PATCH] Added an example showing scope_exit/scope_success/scope_fail usage. --- doc/scope_guards.qbk | 126 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 11 deletions(-) diff --git a/doc/scope_guards.qbk b/doc/scope_guards.qbk index b0f16aa..454983f 100644 --- a/doc/scope_guards.qbk +++ b/doc/scope_guards.qbk @@ -75,11 +75,114 @@ invoke its action if it is destroyed normally, [class_scope_scope_fail] - if it [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.] -Aside from condition function objects, each of the scope guards supports active and inactive state. 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 active state, -but this can be changed by passing `false` as the last argument for the constructor. Scope guards can also be deactivated or -re-activated during their lifetime, which can be useful if the scope guard needs to be activated based on some run time condition -after it is created. +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 +re-activated during their lifetime by calling `set_active` method, which can be useful if the scope guard needs to be activated +based on some run time condition after it is created. class collection { @@ -99,16 +202,17 @@ after it is created. bool inserted; std::tie(it, inserted) = objects.insert(obj); - // Activate rollback guard, if needed + // Activate rollback guard, if the object was inserted rollback_guard.set_active(inserted); + // Notify the object that is a member of the collection. May throw. obj->on_added_to_collection(*this); } }; -The code sample above relies on C++17 [@https://en.cppreference.com/w/cpp/language/class_template_argument_deduction class template -argument deduction (CTAD)] for `scope_fail` 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: +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([&] { @@ -201,8 +305,8 @@ member or a namespace-scope variable).] 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]. 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] -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 ``]. +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