Skip to content

Commit

Permalink
Added an example showing scope_exit/scope_success/scope_fail usage.
Browse files Browse the repository at this point in the history
  • Loading branch information
Lastique committed Jan 28, 2024
1 parent a9bb776 commit 56afb11
Showing 1 changed file with 115 additions and 11 deletions.
126 changes: 115 additions & 11 deletions doc/scope_guards.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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([&]
{
Expand Down Expand Up @@ -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 `<experimental/scope>`].
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
Expand Down

0 comments on commit 56afb11

Please sign in to comment.