BDE 4.14.0 Production release
|
Macros | |
#define | BDLB_SCOPEEXIT_NOEXCEPT_SPEC(...) |
#define | BDLB_SCOPEEXIT_PROCTOR(NAME, EXIT_FUNC) BloombergLP::bdlb::ScopeExitAny NAME((EXIT_FUNC)) |
#define | BDLB_SCOPEEXIT_PRIVATE_CAT(X, Y) BDLB_SCOPEEXIT_PRIVATE_CAT_IMP(X, Y) |
#define | BDLB_SCOPEEXIT_PRIVATE_CAT_IMP(X, Y) X##Y |
#define | BDLB_SCOPEEXIT_PRIVATE_UNIQNUM __LINE__ |
#define | BDLB_SCOPEEXIT_GUARD(EXIT_FUNC) |
Provide a general-purpose proctor object for scope-exit logic.
ScopeExit<bsl::function<void()>>
This component provides a class template mechanism bdlb::ScopeExit
, that will invoke a client supplied function upon its destruction. bdlb::ScopeExit
is intended to facilitate creating scoped-proctors (similar to those found in {bslma
}) that run a user-specified exit function upon their destruction, unless release()
-d. The primary purpose of such a proctor is to execute a guaranteed undo of some operation in case the complete chain of operations was not successful (exception thrown or early return). The proctor may also be used as a guard to unconditionally run its exit function upon exiting a scope, however for such guards dedicated classes are highly recommended (see also {Guard vs Proctor}. In case of a sufficiently functional C++11 or later platform this component also defines a utility bdlb::ScopeExitUtil
containing a factory method for creating bdlb::ScopeExit
objects (makeScopeExit
). This component also defines a type alias bdlb::ScopeExitAny
that is used in C++03 code to create a proctor where the type of the exit function is not known (for example it is the result of a bind expression). Finally, this component defines two macros that allow creating the bdlb::ScopeExit
objects, without mentioning the exit function type, in a uniform way for platforms limited to C++03 and those supporting sufficiently functional C++11 or later, but with a more efficient implementation for the latter. The first macro, BDLB_SCOPEEXIT_PROCTOR
creates a scope proctor with a given variable name and exit function. The second macro, BDLB_SCOPEEXIT_GUARD
creates a scope guard variable of unspecified name given only an exit function argument. See also {Guard vs Proctor}, and {C++03 Restrictions When Exit Function Type is Unknown}.
Guard and Proctor are terminology used by BDE methodology (see below). Because bdlb::ScopeExit
is so general (its exit function is provided by the user) it can be used both as a Guard and as a Proctor. Below are the BDE definitions followed by an explanation of how they apply to this component.
A (scoped) Guard is an object that maintains control over an associated resource (often acquired on construction) and releases that control when the Guard is destroyed (typically at scope exit – either normally or because an exception was thrown). The guard may provide an explicit release method, but – unlike a Proctor – such a release method is not commonly used.
A Proctor is a special kind of guard intended to restore a valid state under abnormal circumstances (e.g., a thrown exception), until a valid state is restored normally, after which the Proctor's responsibility is explicitly released by its client. The Proctor must provide a mechanism to release the resource from management, and – unlike a standard Guard – its management responsibility is typically released prior to its destruction.
A Guard controls the lifetime a resource, it owns it during the resource's complete lifetime, and its purpose is to free up that resource once the work that needed it is done. Guards may provide a release
method for "off
label use" in case the responsibility for freeing up the resource is transferred to some other code, but it is not its common use.
A Proctor is commonly responsible for undoing changes when a locally non-recoverable error occurs. Proctors do not necessarily clean up resources (although they might, as part of restoring a valid state). When all goes well, Proctors do nothing, because when all the work is successfully completed the user code "deactivates" the Proctor (or Proctors) to "commit" the work by calling the release
method.
There are two restrictions when using this component with C++03 and both are related to missing core language features.
The first restriction is related to bdlb::ScopeExit
being a move-only type, and although this class tries its best to emulate move semantics, returning an instance of bdlb::ScopeExit
from a factory function is just not feasible under C++03. It is technically possible, but would require a lot of boilerplate code on the user side, making the use of the component way too cumbersome to use under C++03.
The second restriction is caused by the absence of the type-deducing auto
keyword in C++03. Although we can deduce the type of the exit function in the factory method, without the type deduction capabilities provided by auto
we cannot turn that return type into the type of a variable.
For the above reasons we do not generate or use a factory method in C++03 but rely on bsl::function<void()>
(as described below) to define a type that is able to store and call any exit function.
In case the type of the exit function is known to the programmer (see {Example 2: Using a Scope Exit Guard In C++03}) it is possible to simply provide that type as the template argument for bdlb::ScopeExit
. But if our exit function has an unknown type (such as is the result of a bind
expression) we are facing the same above two restrictions (see also {Example 3: Unknown Exit Function Type In C++03}).
Given the C+03 restrictions, the following design decisions have been made:
bdlb::ScopeExitAny
that is an alias to bdlb::ScopeExit<bsl::function<void()> >
. An instance of bdlb::ScopeExitAny
can be created without having to name the type of the exit function it is constructed from because bsl::function
provides type erasure. The downside is the runtime performance cost of type erasure (virtual function call that might inhibit inlining) and potential memory allocation for stored arguments - all of which are the cost of using bsl::function
.BDLB_SCOPEEXIT_PROCTOR
and BDLB_SCOPEEXIT_GUARD
macros that hide the difference between using a legacy C++03 bdlb::ScopeExitAny
, or C++ auto
type deduction from the factory function call. The macros thereby will select the most efficient bdlb::ScopeExit
type that is feasible on the given compiler platform without having to specify the exact type of the exit function (the template parameter to bdlb::ScopeExit
).The exit function is created as a member of the guard object, so no memory is allocated for storing it. However, if the exit function copy or move constructors require memory allocation, that memory is supplied by the currently installed default allocator. Currently this component does not support custom allocators. The main use-case of a guard object is to be created on the stack and automatic variables are supposed to use the default allocator, therefore no allocator support was added.
This component has been inspired by P0052R6 a since not-adopted ISO C++ Library proposal, but has been extended to support C++03, BDE methodology, and attributes.
This section illustrates intended use of this component.
In this example we assume a C++ compiler supporting sufficiently functional C++11 or later. Suppose we are creating a simple database that stores names and their associated addresses and we store the names and addresses in two separate tables. While adding data, these tables may fail the insertion, in which case we need to roll back the already inserted data, such as if we inserted the address first, we need to remove it if insertion of the associated name fails.
First, we emulate our database access with the following simple functions:
Next, we draw up our complex, customer-creating function signature:
Then we implement it, starting by inserting the address:
Our dummy function returns 42, indicating successful address insertion.
Next, we create a proctor to remove the already inserted address if the name insertion fails:
Then, we attempt to insert the name:
As our dummy insertCustomer
function will fail first time by throwing an exception (when removedAddressId
is zero) we exit this function to the caller's error handling catch
clause. While exiting the function due to the exception, the local stack is unwound. The non-trivial destructors of local variables are invoked (in the opposite order of their creation). In this case, the destructor of addressProctor
invokes its exit function, saving our non-zero addressId
value into the global removedAddressId
variable.
On the second call to this function, because removedAddressId
is now non-zero, insertCustomer
will not fail, and we continue execution here.
Next, if the insertion succeeded we are done, so we need to release the proctor to make the address permanent, after which we can return the ID:
Now we can verify that a first attempt to add a customer fails with the "right" exception and that removedAddressId
is the expected value:
Finally we verify that calling addCustomer11
again succeeds with the right identifier returned, and that removedAddressId
does not change:
Suppose we are in the same situation as in the C++11 or later example, but we have to create a solution that supports C++03 as well.
First, we have to hand-craft a functor that calls removeAddress
with a given ID because C++03 does not support lambdas:
Then, we implement the add customer function for C++03:
The code is almost the same code as was in addCustomer11
(the implementation that requires sufficiently functional C++11 or later platform), except for the proctor variable definition.
Next, we define the proctor variable with an explicitly spelled out type (that uses the functor type template argument), and a functor object initialized with the identifier of the address to remove:
Notice the extra parentheses we had to use to avoid "the most vexing parse" (https://en.wikipedia.org/wiki/Most_vexing_parse) issue. Since we are in C++03, we cannot use (curly) brace initialization to avoid that issue.
Now, we can complete the rest of the addCustomer03
, which is exactly the same as the corresponding part of the addCustomer11
variant:
Finally, we can verify that both during the failing first attempt to add a customer to our imaginary database and the successful second attempt the RemoveAddress
functor based proctor works just as well as the lambda based addCustomer11
variant did:
Suppose that we decide not to write a functor class for removing an address but use the function itself directly with bdlf::BindUtil::bind
and that way keep the roll-back-code near the point of use like lambdas allow us in C++11 and later, albeit with a less elegant syntax.
First, we design our bind expression as bdlf::BindUtil::bind(&removeAddress, addressId)
.
Then, we can even try it to see if it works as intended by calling the result of a bind expression using a constant for the address ID:
Notice the subtle ()
after the bind expression. We immediately call it after creating it (then destroy it). We have to do it this way. We have no idea what its type is so we cannot make a variable for it.
Next, we create yet another customer adding function that differs only in its proctor definition from the addCustomer11
variant:
Because we do not know the type of our exit function (it is "some functor
object of some type", created by bind
) we have to use the bsl::function
based bdlb::ScopeExitAny
:
Consult {C++03 Restrictions When Exit Function Type Is Unknown} to be aware what additional runtime costs this more compact code has compared to a "hand made" functor with a known type.
Note that since we have to take the address of a function to create the bind
-expression-functor we cannot use this format with standard library functions (unless taking their address is explicitly allowed by the C++ standard), and if removeAddress
were an overloaded function the code would not compile as the compiler would not know which address we want.
The rest of the function is the same and omitted for brevity.
Finally, we can verify that bind
and bdlb::ScopeExitAny
based proctor works just as well:
Suppose we have to create portable code that will compile with C++03 as well as C++11 and later compilers. We also want our code to use the more efficient type-deducing auto
with factory-method variant when compiled with a sufficiently functional C++11 or later compiler, and only fall back to the slower bdlb::ScopeExitAny
solution on C++03 compilers.
We still need to use either functor (RemoveAddress
in our examples) or a bind expression for the exit function because C++03 has no lambdas, therefore our portable code cannot use lambdas. But we can choose the easy-to-use BDLB_SCOPEEXIT_PROCTOR
macro and not sprinkle the add customer function with #ifdef
to see which proctor definition to use.
To keep things simple this component provides a single proctor macro instead of two macro names to remember (one for the case case when the type of the exit function is known and one when it isn't). In case the exit function name is known we can just directly use bdlb::ScopeExit< --ExitFunctionType-- >
on any compiler.
First, we start the add customer function as usual:
Then, we define the proctor using a bind expression and the macro:
Alternatively, we could have also written a functor and write the shorter BDLB_SCOPEEXIT_PROCTOR(proctor, RemoveAddress(addressId))
for the proctor.
The rest of the function is the same and omitted for brevity.
Finally, we can verify the easy proctor with the now customary code:
Suppose that we are writing a printing system that is capable of printing out dynamic data structures that can contain numbers, strings, arrays, maps, etc. When printing out data we often have to print delimiters and find that it is really easy to forget to print the closing the delimiter. So we look for a simple way to automate them. We decide we don't want to change the printing of the opening delimiters, just have a way to automate the printing of the close delimiters without worrying about early returns or break
, continue
, or other control flow changes.
First, we create a functor type that prints closing delimiters:
Then, we can use the above functor and a scope exit guard to automate closing of delimiters in the printing functions:
Next, we can move the printing of the opening delimiter and the closing one near each other in code, so it is clearly visible if an opened delimiter is closed:
The macro works in C++03 and C++11 and later, gives the guard variable a unique (but unspecified) name, adds an extra set of parentheses to take care of "the most vexing parse" and suppresses unused variable compiler warnings. The name for the guard variable created is unspecified. Because this is a guard meaning we do not need to call release()
(unlike a proctor), therefore the name is unimportant.
Now, we can just print what goes inside the delimiters, and we are done:
Finally, we can print some templates and verify that the argument delimiters are closed:
#define BDLB_SCOPEEXIT_GUARD | ( | EXIT_FUNC | ) |
Create a local variable with a generated unique name and with a type that is an instantiation of bdlb::ScopeExit
initialized with the specified EXIT_FUNC
. Note that the specific type of bdlb::ScopeExit
used will depend on available language features. The behavior is undefined if this macro is used (expanded) more than once on the same source line. Note that using this macro in another macro more than once is equivalent to using it more than once on the same source line.
#define BDLB_SCOPEEXIT_NOEXCEPT_SPEC | ( | ... | ) |
#define BDLB_SCOPEEXIT_PRIVATE_CAT | ( | X, | |
Y | |||
) | BDLB_SCOPEEXIT_PRIVATE_CAT_IMP(X, Y) |
This macro is for use by BDLB_SCOPEEXIT_GUARD
only to provide unique variable names. It is not undefined by the end of the file but its direct use is not supported under any circumstances.
#define BDLB_SCOPEEXIT_PRIVATE_CAT_IMP | ( | X, | |
Y | |||
) | X##Y |
This macro is for use by BDLB_SCOPEEXIT_GUARD
only to provide unique variable names. It is not undefined by the end of the file but its direct use is not supported under any circumstances.
#define BDLB_SCOPEEXIT_PRIVATE_UNIQNUM __LINE__ |
This macro is for use by BDLB_SCOPEEXIT_GUARD
only to provide unique variable names. It is not undefined by the end of the file but its direct use is not supported under any circumstances.
#define BDLB_SCOPEEXIT_PROCTOR | ( | NAME, | |
EXIT_FUNC | |||
) | BloombergLP::bdlb::ScopeExitAny NAME((EXIT_FUNC)) |
Create a local variable with the specified NAME
with a type that is an instantiation of bdlb::ScopeExit
initialized with the specified EXIT_FUNC
. Note that the specific type of bdlb::ScopeExit
used will depend on available language features.