Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bdlb_scopeexit
[Package bdlb]

Provide a general-purpose proctor object for scope-exit logic. More...

Namespaces

namespace  bdlb

Detailed Description

Outline
Purpose:
Provide a general-purpose proctor object for scope-exit logic.
Classes:
bdlb::ScopeExit executes a function upon destruction
bdlb::ScopeExitAny an alias to ScopeExit<bsl::function<void()>>
bdlb::ScopeExitUtil C++11 or later factory method for creating guards
Macros:
BDLB_SCOPEEXIT_GUARD creates a scope guard using an exit function
BDLB_SCOPEEXIT_PROCTOR creates a scope proctor from name and exit function
See also:
P0052R6 - Generic Scope Guard and RAII Wrapper for the Standard Library http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0052r6.pdf
Description:

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 vs Proctor:
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.
Definitions:
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.
Further Explanation:
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.
C++03 Restrictions When Exit Function Type Is Unknown:
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:
  1. Do not provide the utility and factory function when compiling with C++03.
  2. Provide a type 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.
  3. Provide 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).
Memory Allocation and Relationship with BDE Allocators:
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.
Notes:
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.
Usage Examples:
This section illustrates intended use of this component.
Example 1: Using a Scope Exit Proctor in C++11 or later:
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:
  int removedAddressId = 0;
  int insertAddress(const char *address)
  {
      (void)address;
      return (0 == removedAddressId ? 2 : 3);
  }

  int insertCustomer(const char *name, int addressId)
  {
      (void)name;
      (void)addressId;
      if (0 == removedAddressId) throw 5; // Simulate failure once
      return 7;
  }

  void removeAddress(int id)
  {
      removedAddressId = id;
  }
Next, we draw up our complex, customer-creating function signature:
  int addCustomer11(const char *name, const char *address)
  {
Then we implement it, starting by inserting the address:
      const int addressId = insertAddress(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:
      auto addressProctor = bdlb::ScopeExitUtil::makeScopeExit(
                                         [=](){ removeAddress(addressId); });
Then, we attempt to insert the name:
      const int custId = insertCustomer(name, addressId);
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:
      addressProctor.release();

      return custId;                                                // RETURN
  }
Now we can verify that a first attempt to add a customer fails with the "right" exception and that removedAddressId is the expected value:
  bool seenException = false;
  try {
      addCustomer11("Quibi", "6555 Barton Ave, Los Angeles, CA, 90038");
  }
  catch (int exceptionValue) {
      assert(5 == exceptionValue);
      seenException = true;
  }
  assert(seenException);
  assert(2 == removedAddressId);
Finally we verify that calling addCustomer11 again succeeds with the right identifier returned, and that removedAddressId does not change:
  assert(7 == addCustomer11("Plum Inc.", "1i Imagine Sq, Coppertin, CA"));
  assert(2 == removedAddressId);
Example 2: Using a Scope Exit Guard in C++03:
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:
  class RemoveAddress {
      int d_id;  // the identifier of the address (row) to remove

    public:
      explicit RemoveAddress(int id)
      : d_id(id)
      {
      }

      void operator()() const
      {
          removeAddress(d_id);
      }
  };
Then, we implement the add customer function for C++03:
  int addCustomer03(const char *name, const char *address)
  {
      const int addressId = insertAddress(address);
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:
      bdlb::ScopeExit<RemoveAddress> addrProctor((RemoveAddress(addressId)));
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:
      const int custId = insertCustomer(name, addressId);
      addrProctor.release();

      return custId;                                                // RETURN
  }
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:
  removedAddressId = 0;
  seenException = false;
  try {
      addCustomer03("Quibi", "6555 Barton Ave, Los Angeles, CA, 90038");
  }
  catch (int exceptionValue) {
      assert(5 == exceptionValue);
      seenException = true;
  }
  assert(seenException);
  assert(2 == removedAddressId);

  assert(7 == addCustomer03("Plum Inc.", "1i Imagine Sq, Coppertin, CA"));
  assert(2 == removedAddressId);
Example 3: Unknown Exit Function Type In C++03:
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:
  removedAddressId = 0;
  bdlf::BindUtil::bind(&removeAddress, 11)();
  assert(11 == removedAddressId);
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:
  int addCustomerAny(const char *name, const char *address)
  {
      const int addressId = insertAddress(address);
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:
      bdlb::ScopeExitAny addressProctor(bdlf::BindUtil::bind(&removeAddress,
                                                             addressId));
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:
  removedAddressId = 0;
  seenException = false;
  try {
      addCustomerAny("Quibi", "6555 Barton Ave, Los Angeles, CA, 90038");
  }
  catch (int exceptionValue) {
      assert(5 == exceptionValue);
      seenException = true;
  }
  assert(seenException);
  assert(2 == removedAddressId);

  assert(7 == addCustomerAny("Plum Inc.", "1i Imagine Sq, Coppertin, CA"));
  assert(2 == removedAddressId);
Example 4: Using the Scope Exit Proctor Macro:
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:
  int addCustomerMacro(const char *name, const char *address)
  {
      const int addressId = insertAddress(address);
Then, we define the proctor using a bind expression and the macro:
      BDLB_SCOPEEXIT_PROCTOR(proctor, bdlf::BindUtil::bind(&removeAddress,
                                                           addressId));
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:
  removedAddressId = 0;
  seenException = false;
  try {
      addCustomerMacro("Quibi", "6555 Barton Ave, Los Angeles, CA, 90038");
  }
  catch (int exceptionValue) {
      assert(5 == exceptionValue);
      seenException = true;
  }
  assert(seenException);
  assert(2 == removedAddressId);

  assert(7 == addCustomerMacro("Plum Inc.", "1i Imagine Sq, Coppertin, CA"));
  assert(2 == removedAddressId);
Example 5: Using the Scope Exit Guard Macro:
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:
  class CloseDelimPrinter {
      const char *d_closingChars_p;  // held, not owned

    public:
      explicit CloseDelimPrinter(const char *s)
      : d_closingChars_p(s)
      {
      }

      void operator()() const
      {
          outStream << d_closingChars_p; // To a fixed stream for brevity
      }
  };
Then, we can use the above functor and a scope exit guard to automate closing of delimiters in the printing functions:
  void printTemplateWithArgs(
                           const bsl::string_view>&             templateName,
                           const bsl::vector<bsl::string_view>& args)
  {
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:
      outStream << templateName << '<';
      BDLB_SCOPEEXIT_GUARD(CloseDelimPrinter(">"));
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:
      if (args.empty()) {
          // Safe to just return, the guard takes care of closing the '<'
          return;                                                   // RETURN
      }

      typedef bsl::vector<bsl::string_view>::const_iterator Cit;

      Cit cit = args.begin();
      outStream << *cit;  // Print first argument
      ++cit;

      for (;cit != args.end(); ++cit) {
          outStream << ", " << *cit;  // Print subsequent argument
      }

      const bsl::string_view last = *(args.end() - 1);
      if (last.back() == '>') {
          outStream << ' ';
      }
  }
Finally, we can print some templates and verify that the argument delimiters are closed:
  bsl::vector<bsl::string_view> targs;
  printTemplateWithArgs("TypeList", targs);
  assert(outStreamContent() == "TypeList<>");

  targs.push_back("bsl::string_view");
  printTemplateWithArgs("bsl::vector", targs);
  assert(outStreamContent() == "bsl::vector<bsl::string_view>");

  targs.push_back("bsl::vector<bsl::string_view>");
  printTemplateWithArgs("bsl::unordered_map", targs);
  assert(outStreamContent() ==
     "bsl::unordered_map<bsl::string_view, bsl::vector<bsl::string_view> >");