BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bslma_constructionutil

Detailed Description

Outline

Purpose

Provide methods to construct arbitrarily-typed objects uniformly.

Classes

See also
bslma_allocator, bslma_bslallocator, 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 allocator and an arbitrary number of arguments. These functions are useful for uniformly constructing an object without concern for whether the object's type is allocator-aware (AA) and, if so, whether the allocator is passed at the end of the argument list (trailing allocator-argument convention) or at the start of the argument list, preceeded by bsl::allocator_arg (leading allocator-argument convention). If the type being constructed is not AA or if a non-allocator scalar such as an int or void * is passed to these methods, the allocator argument is discarded and a non-allocator constructor is invoked.

An additional destructiveMove method moves an object to a new address and destroys the object at the original address, often doing so as an efficient memcpy rather than separate move-destroy operations.

Legacy-AA (types with constructors having a parameter of type bslma::Allocator *) and bsl-AA types (types with constructors having a parameter of type bsl::allocator<>) are handled interchangeably by the functions in this component, accepting either a bslma::Allocator* or a bsl::allocator argument and then passing the appropriate type to the constructed type. Thus bsl-AA code can work seamlessly with legacy-AA code, allowing for a smooth transition from the old model to the new one. Note that this component does not directly use bsl::allocator or bsl::polymorphic_allocator because such use would cause a circular dependancy; an allocator is considered bsl-like if it is implicitly convertible from bslma::Allocator * and has a mechanism method that returns a bslma::Allocator * and an allocator is considered pmr-like if it is convertible from bsl::memory_resource * (and therefore also convertible from bslma::Allocator *.

The construct method provided here has roughly the same functionality as the C++20 library function std::uninitialized_construct_using_allocator and the make method has roughly the same functionality as std::make_obj_using_allocator, with the following differences:

Type Traits

The facilities in this component query several type traits of the TYPE being constructed, using that information as follows:

Trait How Used
--------------------------- -----------------------------------------------
bslma::UsesBslmaAllocator If true, the allocator being passed in must be
either convertible to `bslma::Allocator *` or
else have a `mechanism` accessor. If false,
the allocator argument must be convertible to
the `TYPE::allocator_type`.
bslma::HasAllocatorType If true, `TYPE::allocator_type` is assumed to
the type of allocator accepted by `TYPE`'s
constructors.
bsl::uses_allocator<TYPE,A> If true, then `A` can be used as an allocator
to construct `TYPE`.
bslmf::UsesAllocatorArgT If true, an allocator argument is passed as the
second argument to the constructor, preceeded
by a `bsl::allocator_arg` tag; otherwise the
allocator is passed as the last argument to the
constructor.
bslmf::IsBitwiseMoveable If true, `destructiveMove` is implemented as a
simple `memcpy`, rather than as a move-destroy
sequence.
Definition bslma_allocator.h:457
Definition bslma_usesbslmaallocator.h:343

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 an instance of bsl::allocator 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:

/// 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.
template <class TYPE>
class MyContainerProctor {
// DATA
bsl::allocator<TYPE> d_allocator;
TYPE *d_address_p; // proctored memory
private:
// NOT IMPLEMENTED
MyContainerProctor(const MyContainerProctor&); // = delete
MyContainerProctor& operator=(const MyContainerProctor&); // = delete
public:
// CREATORS
/// 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`.
MyContainerProctor(const bsl::allocator<TYPE> allocator, TYPE *address)
: d_allocator(allocator)
, d_address_p(address)
{
}
/// 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.
~MyContainerProctor()
{
if (d_address_p) {
d_allocator.deallocate(d_address_p, 1);
}
}
// MANIPULATORS
/// Release from management the block of memory currently managed by
/// this proctor. If no memory is currently being managed, this
/// method has no effect.
void release()
{
d_address_p = 0;
}
};
Definition bslma_bslallocator.h:580
void deallocate(TYPE *p, std::size_t n=1)
Definition bslma_bslallocator.h:1039

Then, we create a container class that holds a single element and uses bsl::allocator to supply memory:

/// This class provides a container that always holds exactly one
/// element, dynamically allocated using the specified `bslma`
/// allocator.
template <class TYPE>
class MyContainer {
// DATA
bsl::allocator<TYPE> d_allocator;
TYPE *d_value_p;
/// Return the address of a new element that was allocated from this
/// container's allocator and initialized with the optionally
/// specified `value`, or default-initialized if `value` is not
/// specified. If `TYPE` is AA, this container's allocator is used
/// to construct the new element.
TYPE *createElement();
TYPE *createElement(const TYPE& value);
public:
typedef bsl::allocator<TYPE> allocator_type;
// CREATORS
/// Create a container with a default-constructed element.
/// Optionally specify a `allocator` used to supply memory.
explicit
MyContainer(const allocator_type& allocator = allocator_type())
: d_allocator(allocator), d_value_p(createElement()) { }
/// Create a container having an element constructed from the
/// specified `value`. Optionally specify an `allocator` to supply
/// memory both for the container and for the contained element.
explicit
MyContainer(const TYPE& value,
const allocator_type& allocator = allocator_type())
: d_allocator(allocator), d_value_p(createElement(value)) { }
/// Create a container having the same value as the specified
/// `original` object. Optionally specify a `allocator` used
/// to supply memory. If `allocator` is 0, the currently
/// installed default allocator is used.
MyContainer(const MyContainer& original,
const allocator_type& allocator = allocator_type())
: d_allocator(allocator)
, d_value_p(createElement(*original.d_value_p)) { }
/// Destroy this object.
~MyContainer();
// MANIPULATORS
/// Assign to this object the value of the specified `rhs` object,
/// and return a reference providing modifiable access to this
/// object.
MyContainer& operator=(const TYPE& rhs);
MyContainer& operator=(const MyContainer& rhs);
/// Return a non-`const` reference to the element contained in this
/// object.
TYPE& front()
{
return *d_value_p;
}
// ACCESSORS
/// Return a `const` reference to the element contained in this
/// object.
const TYPE& front() const
{
return *d_value_p;
}
/// Return the allocator used by this object to supply memory.
allocator_type get_allocator() const
{
return d_allocator;
}
// etc.
};

Next, we implement the private createElement members that allocate memory and construct a TYPE object in the allocated memory. We perform the allocation using the allocate method of bsl::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>
TYPE *MyContainer<TYPE>::createElement()
{
TYPE *value_p = d_allocator.allocate(1);
MyContainerProctor<TYPE> proctor(d_allocator, value_p);
// Call 'construct' passing the allocator but no constructor
// arguments.
bslma::ConstructionUtil::construct(value_p, d_allocator);
proctor.release();
return value_p;
}
template <class TYPE>
TYPE *MyContainer<TYPE>::createElement(const TYPE& value)
{
TYPE *value_p = d_allocator.allocate(1);
MyContainerProctor<TYPE> proctor(d_allocator, value_p);
// Call 'construct' passing the allocator and 'value' arguments.
bslma::ConstructionUtil::construct(value_p, d_allocator, value);
proctor.release();
return value_p;
}
BSLS_ANNOTATION_NODISCARD pointer allocate(size_type n, const void *hint=0)
Definition bslma_bslallocator.h:1032
static void construct(TARGET_TYPE *address, const ALLOCATOR &allocator)
Definition bslma_constructionutil.h:1243

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.deallocate(d_value_p, 1);
}

Next, the assignment operator needs to assign the value without modifying the allocator.

template <class TYPE>
MyContainer<TYPE>& MyContainer<TYPE>::operator=(const TYPE& rhs)
{
if (&rhs != d_value_p) {
*d_value_p = rhs;
}
return *this;
}
template <class TYPE>
MyContainer<TYPE>& MyContainer<TYPE>::operator=(const MyContainer& rhs)
{
return operator=(*rhs.d_value_p);
}

Finally, we perform a simple test of MyContainer, instantiating it with element type int:

int main()
{
MyContainer<int> C1(123, &testAlloc);
assert(C1.get_allocator() == &testAlloc);
assert(C1.front() == 123);
MyContainer<int> C2(C1);
assert(C2.get_allocator() == bslma::Default::defaultAllocator());
assert(C2.front() == 123);
return 0;
}
Definition bslma_testallocator.h:384
static Allocator * defaultAllocator()
Definition bslma_default.h:889

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. Unlike the MyContainer template, MyType allocates memory using the bslma::Allocator * (legacy) allocator model instead of the bsl::allocator (bsl) allocator model:

#include <bslma_default.h>
class MyType {
// DATA
bslma::Allocator *d_allocator_p;
int d_value;
// ...
public:
// TRAITS
// CREATORS
/// 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.
explicit MyType(bslma::Allocator *basicAllocator = 0)
: d_allocator_p(bslma::Default::allocator(basicAllocator))
, d_value()
{
// ...
}
/// Create a `MyType` object having the specified `value`.
/// Optionally specify a `basicAllocator` used to supply memory. If
/// `basicAllocator` is 0, the currently installed default allocator
/// is used.
explicit MyType(int value,
bslma::Allocator *basicAllocator = 0)
: d_allocator_p(bslma::Default::allocator(basicAllocator))
, d_value(value)
{
// ...
}
/// 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.
MyType(const MyType& original, bslma::Allocator *basicAllocator = 0)
: d_allocator_p(bslma::Default::allocator(basicAllocator))
, d_value(original.value())
{
// ...
}
// ...
// ACCESSORS
/// Return the allocator used by this object to supply memory.
bslma::Allocator *allocator() const
{
return d_allocator_p;
}
/// Return the value of this object.
int value() const
{
return d_value;
}
// ...
};
#define BSLMF_NESTED_TRAIT_DECLARATION(t_TYPE, t_TRAIT)
Definition bslmf_nestedtraitdeclaration.h:231
Definition balxml_encoderoptions.h:68

Finally, we instantiate MyContainer using MyType and verify that, when we provide an allocator to the constructor of the container, the same allocator is passed to the constructor of the contained element. Because the container and the element implement different allocator models, the invocation of bslma::ConstructionUtil::construct automatically adapts the bsl::allocator held by the container to a bslma::Allocator pointer expected by the 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()
{
MyContainer<MyType> C1(&testAlloc); // extended default constructor
assert(C1.get_allocator() == &testAlloc);
assert(C1.front().allocator() == &testAlloc);
assert(C1.front().value() == 0);
MyContainer<MyType> C2(MyType(22), &testAlloc); // value constructor
assert(C2.get_allocator() == &testAlloc);
assert(C2.front().allocator() == &testAlloc);
assert(C2.front().value() == 22);
MyContainer<MyType> C3(C2);
assert(C3.get_allocator() != C2.get_allocator());
assert(C3.get_allocator() == bslma::Default::defaultAllocator());
assert(C3.front().allocator() != C1.front().allocator());
assert(C3.front().allocator() == bslma::Default::defaultAllocator());
assert(C3.front().value() == 22);
MyContainer<MyType> C4(C2, &testAlloc2);
assert(C4.get_allocator() == &testAlloc2);
assert(C4.front().allocator() == &testAlloc2);
assert(C4.front().value() == 22);
}

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. The functor (known as the listener) is called each time the wrapped object is changes. We store the object directly as a member variable, instead of using an uninitialized buffer, to avoid a separate construction step:

/// 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.
template <class TYPE, class FUNC>
class MyTriggeredWrapper {
// DATA
TYPE d_value;
FUNC d_listener;
public:
typedef bsl::allocator<> allocator_type;
// CREATORS
/// 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 `allocator` to supply
/// memory; otherwise the current default allocator is used. If
/// `TYPE` is not allocator aware, `allocator` is ignored.
explicit
MyTriggeredWrapper(const FUNC& f,
const allocator_type& allocator = allocator_type());
MyTriggeredWrapper(const TYPE& v,
const FUNC& f,
const allocator_type& allocator = allocator_type());
/// Create a copy of the specified `original`. Optionally specify
/// `allocator` to supply memory; otherwise the current
/// default allocator is used.
MyTriggeredWrapper(const MyTriggeredWrapper& original,
const allocator_type& allocator = allocator_type());
/// Destroy the wrapped object and listener.
~MyTriggeredWrapper()
{
}
// MANIPULATORS
/// Assign to the wrapped value 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.
MyTriggeredWrapper& operator=(const TYPE& rhs);
MyTriggeredWrapper& operator=(const MyTriggeredWrapper& rhs);
/// Set the wrapped value to the specified `value` and invoke the
/// listener with the new value.
void setValue(const TYPE& value);
// ACCESSORS
/// Return a reference providing read-only access to the wrapped
/// value.
const TYPE& value() const
{
return d_value;
}
/// Return a reference providing read-only access to the listener.
const FUNC& listener() const
{
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 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 straightforward:

template <class TYPE, class FUNC>
MyTriggeredWrapper<TYPE, FUNC>::MyTriggeredWrapper(
const FUNC& f,
const allocator_type& allocator)
: d_value(bslma::ConstructionUtil::make<TYPE>(allocator))
, d_listener(f)
{
}
template <class TYPE, class FUNC>
MyTriggeredWrapper<TYPE, FUNC>::MyTriggeredWrapper(
const TYPE& v,
const FUNC& f,
const allocator_type& allocator)
: d_value(bslma::ConstructionUtil::make<TYPE>(allocator, v))
, d_listener(f)
{
}
template <class TYPE, class FUNC>
MyTriggeredWrapper<TYPE, FUNC>::MyTriggeredWrapper(
const MyTriggeredWrapper& other,
const allocator_type& allocator)
: d_value(bslma::ConstructionUtil::make<TYPE>(allocator, other.value()))
, d_listener(other.d_listener)
{
}

Note that, for d_value to be constructed with the correct allocator, the compiler must construct the result returned from make directly into the d_value variable, an optimization 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 C++11 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();
}
int main()
{
MyTriggeredWrapper<MyContainer<int>,
void (*)(const MyContainer<int>&)>
wrappedContainer(myListener, &testAlloc);
assert(&testAlloc == wrappedContainer.value().get_allocator());
wrappedContainer = MyContainer<int>(99);
assert(99 == lastSeen);
}