Component of the Week #20: bdlb_scopeexit
- Summary:
Provides a general-purpose proctor object that executes a function when exiting a scope.
The bdlb_scopeexit
component provides a mechanism to execute cleanup or rollback code when
exiting a scope. For example, in the below snippet, the lambda will be invoked
if myFunction returns or if it exits via an exception, thus guaranteeing
that d_resource is released in either case:
int MyClass::myFunction()
{
d_resource.acquire();
bdlb::ScopeExit guard([&d_resource]() { d_resource.release(); });
// Use the resource (can fail by throwing an exception)
// . . .
}
Note that the above snippet requires at least C++17. Prior to C++17,
bdlb::ScopeExit can still be used, with some caveats. The template
parameter of bdlb::ScopeExit, which is the type of the callable, cannot be
omitted prior to C++17. To facilitate the use of bdlb::ScopeExit prior to
C++17, this component also provides two macros, which will be described below.
A key concept in this component is the distinction between guards and proctors:
Guard: Designed to unconditionally run exit code when exiting a scope.
Proctor: Designed to execute rollback logic on abnormal exit but can be explicitly released (using the
releasemethod) to avoid executing the exit function when operations succeed.
bdlb::ScopeExit can be used as either a guard or a proctor; the user is not
obligated to call release.
The component provides two convenient macros that are named after these concepts and do not require the user to write the type of the callable:
BDLB_SCOPEEXIT_GUARD: Creates aScopeExitobject with an unspecified name, which therefore can’t be released.BDLB_SCOPEEXIT_PROCTOR: Creates aScopeExitobject with the given name, thus allowingreleaseto be called.
The macros are recommended for use in code bases that must support C++03. In
C++03, the macros use bsl::function<void()> internally, while in later
language versions, they deduce the actual callable type, typically resulting in
more efficient code.
Here’s an example showing how to use ScopeExit as a proctor to implement a
transactional operation:
#include <bdlb_scopeexit.h>
#include <bsl_string.h>
using namespace BloombergLP;
/// This class represents a relational database.
class Database {
public:
/// Insert the specified `address` into the address table and return
/// the ID of the inserted row.
int insertAddress(const bsl::string& address);
/// Insert a customer into the customer table having the specified
/// `name`, whose address has the specified `addressId` in the address
/// table. If `name` is not valid, throw an exception that would be
/// caught by an exception declaration of type `bsl::runtime_error`.
void insertCustomer(const bsl::string& name, int addressId);
/// Delete from the address table the row having the specified
/// `addressId`.
void removeAddress(int addressId);
};
/// A callable type that removes a specified address, to be called by
/// `addCustomer` in case of failure.
struct RemoveAddress {
Database& d_db;
int d_addressId;
RemoveAddress(Database& db, int addressId);
void operator()() const;
};
/// Add to the specified `db` a customer with the specified `name` and
/// `address`. If this operation fails, `db` is not modified and any
/// thrown exception is propagated.
void addCustomer(Database& db,
const bsl::string& name,
const bsl::string& address)
{
// First, add the address to get its ID.
const int addressId = db.insertAddress(address);
// Create a scope proctor that will remove the address on failure.
BDLB_SCOPEEXIT_PROCTOR(addressProctor, RemoveAddress(db, addressId));
// Try to add the customer (may throw).
db.insertCustomer(name, addressId);
// If we get here, everything succeeded, so release the proctor to
// avoid removing the address.
addressProctor.release();
}
In this example, BDLB_SCOPEEXIT_PROCTOR creates a proctor object that
will automatically call the cleanup callable (removing the address) if an
exception occurs during customer insertion. If customer insertion succeeds,
we explicitly call release() to prevent the cleanup from happening. Note
that in C++11 and later, a lambda can be used as the callable, enabling cleanup
to be expressed much more easily.
For more details, see the documentation for bdlb_scopeexit.