Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bslma_constructionutil
[Package bslma]

Provide methods to construct arbitrarily-typed objects uniformly. More...

Namespaces

namespace  bslma

Detailed Description

Outline
Purpose:
Provide methods to construct arbitrarily-typed objects uniformly.
Classes:
bslma::ConstructionUtil namespace for methods to construct objects
See also:
Component bslma_allocatortraits, Component bslma_destructionutil
Description:
This component provides a struct, bslma::ConstructionUtil, that serves as a namespace for utility functions to construct objects of an arbitrary (template parameter) type, given an arbitrary number of arguments. These functions are useful in implementing allocator_trait classes that, in turn, are used in implementing generic components such as containers. How a type is constructed may depend on several type traits. The traits under consideration by this component are:
  Trait                                         Description
  --------------------------------------------  -----------------------------
  bsl::is_trivially_default_constructible       "TYPE has the trivial default
                                                constructor trait", or
                                                "TYPE has a trivial default
                                                constructor"

  bslma::UsesBslmaAllocator                     "the 'TYPE' constructor takes
                                                an allocator argument", or
                                                "'TYPE' supports 'bslma'-
                                                style allocators"

  bslmf::UsesAllocatorArgT                      "the 'TYPE' constructor takes
                                                an allocator argument", and
                                                optionally passes allocators
                                                as the first two arguments to
                                                each constructor, where the
                                                tag type 'allocator_arg_t' is
                                                first, and the allocator type
                                                is second

  bsl::is_trivially_copyable                    "TYPE has the bitwise
                                                copyable trait", or
                                                "TYPE is bitwise copyable"
                                                (implies that it has a
                                                trivial destructor too)

  bslmf::IsBitwiseMoveable                      "TYPE has the bitwise
                                                movable trait", or
                                                "TYPE is bitwise movable"
This component provides full support for in-place construction of objects such that the object type's allocator policy is respected and all arguments are perfectly forwarded to the appropriate constructor. This component also provides partial support for the C++20 std::make_obj_using_allocator utility via the (overloaded) bslma::ConstructionUtil::make method. Currently, make supports only default construction and construction from one (non-allocator) argument.
Usage:
This section illustrates intended use of this component.
Example 1: Using bslma::ConstructionUtil to Implement a Container:
This example demonstrates the intended use of bslma::ConstructionUtil to implement a simple container class that uses the bslma::Allocator protocol for memory management.
First, because allocation and construction are done in two separate steps, we need to define a proctor type that will deallocate the allocated memory in case the constructor throws an exception:
  template <class TYPE>
  class MyContainerProctor {
      // This class implements a proctor to release memory allocated during
      // the construction of a 'MyContainer' object if the constructor for
      // the container's data element throws an exception.  Such a proctor
      // should be 'release'd once the element is safely constructed.

      // DATA
      bslma::Allocator *d_allocator_p;
      TYPE             *d_address_p;    // proctored memory

    private:
      // NOT IMPLEMENTED
      MyContainerProctor(const MyContainerProctor&);             // = delete
      MyContainerProctor& operator=(const MyContainerProctor&);  // = delete

    public:
      // CREATORS
      MyContainerProctor(bslma::Allocator *allocator, TYPE *address)
          // Create a proctor that conditionally manages the memory at the
          // specified 'address', and that uses the specified 'allocator' to
          // deallocate the block of memory (if not released -- see
          // 'release') upon destruction.  The behavior is undefined unless
          // 'allocator' is non-zero and supplied the memory at 'address'.
      : d_allocator_p(allocator)
      , d_address_p(address)
      {
      }

      ~MyContainerProctor()
          // Destroy this proctor, and deallocate the block of memory it
          // manages (if any) by invoking the 'deallocate' method of the
          // allocator that was supplied at construction of this proctor.  If
          // no memory is currently being managed, this method has no effect.
      {
          if (d_address_p) {
              d_allocator_p->deallocate(d_address_p);
          }
      }

      // MANIPULATORS
      void release()
          // Release from management the block of memory currently managed by
          // this proctor.  If no memory is currently being managed, this
          // method has no effect.
      {
          d_address_p = 0;
      }
  };
Then, we create a container class that holds a single element and uses bslma allocators:
  #include <bslma_constructionutil.h>

  template <class TYPE>
  class MyContainer {
      // This class provides a container that always holds exactly one
      // element, dynamically allocated using the specified 'bslma'
      // allocator.

      // DATA
      TYPE             *d_value_p;
      bslma::Allocator *d_allocator_p;

    public:
      // TRAITS
      BSLMF_NESTED_TRAIT_DECLARATION(MyContainer, bslma::UsesBslmaAllocator);

      // CREATORS
      explicit MyContainer(bslma::Allocator *basicAllocator = 0);
          // Create a container with a default-constructed element.
          // Optionally specify a 'basicAllocator' used to supply memory.  If
          // 'basicAllocator' is 0, the currently installed default allocator
          // is used.

      template <class OTHER>
      explicit MyContainer(
          BSLS_COMPILERFEATURES_FORWARD_REF(OTHER) value,
          typename bsl::enable_if<bsl::is_convertible<OTHER, TYPE>::value,
                                  void *>::type * = 0)
          // Create a container with an element constructed by (perfectly)
          // forwarding the specified 'value' and that uses the currently
          // installed default allocator to supply memory.  Note that this
          // constructor participates in overload resolution only if 'OTHER'
          // is implicitly convertible to 'TYPE'.
      : d_allocator_p(bslma::Default::defaultAllocator())
      {
          d_value_p =
              static_cast<TYPE *>(d_allocator_p->allocate(sizeof(TYPE)));

          MyContainerProctor<TYPE> proctor(d_allocator_p, d_value_p);

          // Call 'construct' by forwarding 'value'.

          bslma::ConstructionUtil::construct(
              d_value_p,
              d_allocator_p,
              BSLS_COMPILERFEATURES_FORWARD(OTHER, value));
          proctor.release();
      }

      template <class OTHER>
      explicit MyContainer(
          BSLS_COMPILERFEATURES_FORWARD_REF(OTHER)  value,
          bslma::Allocator                         *basicAllocator);
          // Create a container with an element constructed by (perfectly)
          // forwarding the specified 'value' and that uses the specified
          // 'basicAllocator' to supply memory.  If 'basicAllocator' is 0,
          // the currently installed default allocator is used.  Note that
          // this constructor participates in overload resolution only if
          // 'OTHER' is implicitly convertible to 'TYPE'.

      MyContainer(const MyContainer&  original,
                  bslma::Allocator   *basicAllocator = 0);
          // Create a container having the same value as the specified
          // 'original' object.  Optionally specify a 'basicAllocator' used
          // to supply memory.  If 'basicAllocator' is 0, the currently
          // installed default allocator is used.

      ~MyContainer();
          // Destroy this object.

      // MANIPULATORS
      MyContainer& operator=(const TYPE& rhs);
      MyContainer& operator=(const MyContainer& rhs);
          // Assign to this object the value of the specified 'rhs' object,
          // and return a reference providing modifiable access to this
          // object.

      TYPE& front()
          // Return a non-'const' reference to the element contained in this
          // object.
      {
          return *d_value_p;
      }

      // ACCESSORS
      const TYPE& front() const
          // Return a 'const' reference to the element contained in this
          // object.
      {
          return *d_value_p;
      }

      bslma::Allocator *allocator() const
          // Return the allocator used by this object to supply memory.
      {
          return d_allocator_p;
      }

      // etc.
  };
Next, we implement the constructors that allocate memory and construct a TYPE object in the allocated memory. We perform the allocation using the allocate method of bslma::Allocator and the construction using the construct method of ConstructionUtil that provides the correct semantics for passing the allocator to the constructed object when appropriate:
  template <class TYPE>
  MyContainer<TYPE>::MyContainer(bslma::Allocator *basicAllocator)
  : d_allocator_p(bslma::Default::allocator(basicAllocator))
  {
      d_value_p = static_cast<TYPE *>(d_allocator_p->allocate(sizeof(TYPE)));
      MyContainerProctor<TYPE> proctor(d_allocator_p, d_value_p);

      // Call 'construct' with no constructor arguments (aside from the
      // allocator).

      bslma::ConstructionUtil::construct(d_value_p, d_allocator_p);
      proctor.release();
  }

  template <class TYPE>
  template <class OTHER>
  MyContainer<TYPE>::MyContainer(
                    BSLS_COMPILERFEATURES_FORWARD_REF(OTHER)  value,
                    bslma::Allocator                         *basicAllocator)
  : d_allocator_p(bslma::Default::allocator(basicAllocator))
  {
      d_value_p = static_cast<TYPE *>(d_allocator_p->allocate(sizeof(TYPE)));
      MyContainerProctor<TYPE> proctor(d_allocator_p, d_value_p);

      // Call 'construct' by forwarding 'value'.

      bslma::ConstructionUtil::construct(
          d_value_p,
          d_allocator_p,
          BSLS_COMPILERFEATURES_FORWARD(OTHER, value));
      proctor.release();
  }
Then, we define the copy constructor for MyContainer. Note that we don't propagate the allocator from the original container, but use basicAllocator instead:
  template <class TYPE>
  MyContainer<TYPE>::MyContainer(const MyContainer&  original,
                                 bslma::Allocator   *basicAllocator)
  : d_allocator_p(bslma::Default::allocator(basicAllocator))
  {
      d_value_p = static_cast<TYPE *>(d_allocator_p->allocate(sizeof(TYPE)));
      MyContainerProctor<TYPE> proctor(d_allocator_p, d_value_p);

      // Call 'construct' so as to copy-construct the element contained by
      // 'original'.

      bslma::ConstructionUtil::construct(d_value_p,
                                         d_allocator_p,
                                         *original.d_value_p);
      proctor.release();
  }
Now, the destructor destroys the object and deallocates the memory used to hold the element using the allocator:
  template <class TYPE>
  MyContainer<TYPE>::~MyContainer()
  {
      d_value_p->~TYPE();
      d_allocator_p->deallocate(d_value_p);
  }
Next, the assignment operator needs to assign the value without modifying the allocator.
  template <class TYPE>
  MyContainer<TYPE>& MyContainer<TYPE>::operator=(const TYPE& rhs)
  {
      *d_value_p = rhs;
      return *this;
  }

  template <class TYPE>
  MyContainer<TYPE>& MyContainer<TYPE>::operator=(const MyContainer& rhs)
  {
      *d_value_p = *rhs.d_value_p;
      return *this;
  }
Finally, we perform a simple test of MyContainer, instantiating it with element type int:
  int main()
  {
      bslma::TestAllocator testAlloc;
      MyContainer<int>     C1(123, &testAlloc);
      assert(C1.allocator() == &testAlloc);
      assert(C1.front()     == 123);

      MyContainer<int> C2(C1);
      assert(C2.allocator() == bslma::Default::defaultAllocator());
      assert(C2.front()     == 123);

      return 0;
  }
Example 2: bslma Allocator Propagation:
This example demonstrates that MyContainer does indeed propagate the allocator to its contained element.
First, we create a representative element class, MyType, that allocates memory using the bslma allocator protocol:
  #include <bslma_default.h>

  class MyType {

      // DATA
      // ...
      bslma::Allocator *d_allocator_p;

    public:
      // TRAITS
      BSLMF_NESTED_TRAIT_DECLARATION(MyType, bslma::UsesBslmaAllocator);

      // CREATORS
      explicit MyType(bslma::Allocator *basicAllocator = 0)
          // Create a 'MyType' object having the default value.  Optionally
          // specify a 'basicAllocator' used to supply memory.  If
          // 'basicAllocator' is 0, the currently installed default allocator
          // is used.
      : d_allocator_p(bslma::Default::allocator(basicAllocator))
      {
          // ...
      }

      MyType(const MyType& original, bslma::Allocator *basicAllocator = 0)
          // Create a 'MyType' object having the same value as the specified
          // 'original' object.  Optionally specify a 'basicAllocator' used
          // to supply memory.  If 'basicAllocator' is 0, the currently
          // installed default allocator is used.
      : d_allocator_p(bslma::Default::allocator(basicAllocator))
      {
          (void) original;
          // ...
      }

      // ...

      // ACCESSORS
      bslma::Allocator *allocator() const
          // Return the allocator used by this object to supply memory.
      {
          return d_allocator_p;
      }

      // ...
  };
Finally, we instantiate MyContainer using MyType and verify that, when we provide the address of an allocator to the constructor of the container, the same address is passed to the constructor of the contained element. We also verify that, when the container is copy-constructed without supplying an allocator, the copy uses the default allocator, not the allocator from the original object. Moreover, we verify that the element stored in the copy also uses the default allocator:
  int main()
  {
      bslma::TestAllocator testAlloc;
      MyContainer<MyType>  C1(&testAlloc);
      assert(C1.allocator()         == &testAlloc);
      assert(C1.front().allocator() == &testAlloc);

      MyContainer<MyType> C2(C1);
      assert(C2.allocator()         != C1.allocator());
      assert(C2.allocator()         == bslma::Default::defaultAllocator());
      assert(C2.front().allocator() != &testAlloc);
      assert(C2.front().allocator() == bslma::Default::defaultAllocator());

      return 0;
  }
Example 3: Constructing into Non-heap Memory:
This example demonstrates using bslma::ConstructionUtil::make to implement a simple wrapper class that contains a single item that might or might not use the bslma allocator protocol.
First, we define a wrapper class that holds an object and a functor and calls the functor (called the listener) each time the wrapped object is changed. We store the object directly as a member variable, instead of using an uninitialized buffer, to avoid a separate construction step:
  template <class TYPE, class FUNC>
  class MyTriggeredWrapper {
      // This class is a wrapper around an object of the specified 'TYPE'
      // that triggers a call to an object, called the "listener", of the
      // specified 'FUNC' invocable type whenever the wrapped object is
      // changed.

      // DATA
      TYPE d_value;
      FUNC d_listener;

    public:
      // CREATORS
      explicit MyTriggeredWrapper(const FUNC&       f,
                                  bslma::Allocator *basicAllocator = 0);
      MyTriggeredWrapper(const TYPE&       v,
                         const FUNC&       f,
                         bslma::Allocator *basicAllocator = 0);
          // Create an object with the specified 'f' as the listener to be
          // called when a change is triggered.  Optionally specify 'v' as
          // the wrapped value; otherwise the wrapped value is default
          // constructed.  Optionally specify 'basicAllocator' to supply
          // memory; otherwise the current default allocator is used.  If
          // 'TYPE' is not allocator aware, 'basicAllocator' is ignored.

      MyTriggeredWrapper(const MyTriggeredWrapper&  original,
                         bslma::Allocator          *basicAllocator = 0);
          // Create a copy of the specified 'original'.  Optionally specify
          // 'basicAllocator' to supply memory; otherwise the current
          // default allocator is used.

      ~MyTriggeredWrapper()
          // Destroy the wrapped object and listener.
      {
      }

      // MANIPULATORS
      MyTriggeredWrapper& operator=(const TYPE& rhs);
      MyTriggeredWrapper& operator=(const MyTriggeredWrapper& rhs);
          // Assign the wrapped value to the value of the specified 'rhs',
          // invoke the listener with the new value, and return a reference
          // providing modifiable access to this object.  Note that the
          // listener itself is not assigned.

      void setValue(const TYPE& value);
          // Set the wrapped value to the specified 'value' and invoke the
          // listener with the new value.

      // ACCESSORS
      const TYPE& value() const
          // Return a reference providing read-only access to the wrapped
          // value.
      {
          return d_value;
      }

      const FUNC& listener() const
          // Return a reference providing read-only access to the listener.
      {
          return d_listener;
      }
  };
Next, we define the constructors such that they initialize d_value using the specified allocator if and only if TYPE accepts an allocator. The bslma::ConstructionUtil::make family of functions encapsulate all of the metaprogramming that detects whether or not TYPE uses an allocator and, if so, which construction protocol it uses (allocator at the front or at the back of the argument list), making all three constructors straight- forward:
  template <class TYPE, class FUNC>
  MyTriggeredWrapper<TYPE, FUNC>::MyTriggeredWrapper(
                                            const FUNC&       f,
                                            bslma::Allocator *basicAllocator)
  : d_value(bslma::ConstructionUtil::make<TYPE>(basicAllocator))
  , d_listener(f)
  {
  }

  template <class TYPE, class FUNC>
  MyTriggeredWrapper<TYPE, FUNC>::MyTriggeredWrapper(
                                            const TYPE&       v,
                                            const FUNC&       f,
                                            bslma::Allocator *basicAllocator)
  : d_value(bslma::ConstructionUtil::make<TYPE>(basicAllocator, v))
  , d_listener(f)
  {
  }

  template <class TYPE, class FUNC>
  MyTriggeredWrapper<TYPE, FUNC>::MyTriggeredWrapper(
                                   const MyTriggeredWrapper&  other,
                                   bslma::Allocator          *basicAllocator)
  : d_value(bslma::ConstructionUtil::make<TYPE>(basicAllocator,
                                                other.value()))
  , d_listener(other.d_listener)
  {
  }
Note that, in order for d_value to be constructed with the correct allocator, the compiler must construct the result returned by make directly into the d_value variable, an optimization formerly known prior to C++17 as "copy elision". This optimization is required by the C++17 standard and is optional in pre-2017 standards, but is implemented in all of the compilers for which this component is expected to be used at Bloomberg.
Next, we implement the assignment operators, which simply call setValue:
  template <class TYPE, class FUNC>
  MyTriggeredWrapper<TYPE, FUNC>&
  MyTriggeredWrapper<TYPE, FUNC>::operator=(const TYPE& rhs)
  {
      setValue(rhs);
      return *this;
  }

  template <class TYPE, class FUNC>
  MyTriggeredWrapper<TYPE, FUNC>&
  MyTriggeredWrapper<TYPE, FUNC>::operator=(const MyTriggeredWrapper& rhs)
  {
      setValue(rhs.value());
      return *this;
  }
Then, we implement setValue, which calls the listener after modifying the value:
  template <class TYPE, class FUNC>
  void MyTriggeredWrapper<TYPE, FUNC>::setValue(const TYPE& value)
  {
      d_value = value;
      d_listener(d_value);
  }
Finally, we check our work by creating a listener for MyContainer<int> that stores its last-seen value in a known location and a wrapper around MyContainer<int> to test it:
  int lastSeen = 0;
  void myListener(const MyContainer<int>& c)
  {
      lastSeen = c.front();
  }

  void main()
  {
      bslma::TestAllocator testAlloc;
      MyTriggeredWrapper<MyContainer<int>,
                         void (*)(const MyContainer<int>&)>
                           wrappedContainer(myListener, &testAlloc);
      assert(&testAlloc == wrappedContainer.value().allocator());

      wrappedContainer = MyContainer<int>(99);

      assert(99 == lastSeen);
  }