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:
-
- 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 {
bslma::Allocator *d_allocator_p;
TYPE *d_address_p;
private:
MyContainerProctor(const MyContainerProctor&);
MyContainerProctor& operator=(const MyContainerProctor&);
public:
MyContainerProctor(bslma::Allocator *allocator, TYPE *address)
: d_allocator_p(allocator)
, d_address_p(address)
{
}
~MyContainerProctor()
{
if (d_address_p) {
d_allocator_p->deallocate(d_address_p);
}
}
void release()
{
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 {
TYPE *d_value_p;
bslma::Allocator *d_allocator_p;
public:
BSLMF_NESTED_TRAIT_DECLARATION(MyContainer, bslma::UsesBslmaAllocator);
explicit MyContainer(bslma::Allocator *basicAllocator = 0);
template <class OTHER>
explicit MyContainer(
BSLS_COMPILERFEATURES_FORWARD_REF(OTHER) value,
typename bsl::enable_if<bsl::is_convertible<OTHER, TYPE>::value,
void *>::type * = 0)
: 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);
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);
MyContainer(const MyContainer& original,
bslma::Allocator *basicAllocator = 0);
~MyContainer();
MyContainer& operator=(const TYPE& rhs);
MyContainer& operator=(const MyContainer& rhs);
TYPE& front()
{
return *d_value_p;
}
const TYPE& front() const
{
return *d_value_p;
}
bslma::Allocator *allocator() const
{
return d_allocator_p;
}
};
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);
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);
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);
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: 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 {
TYPE d_value;
FUNC d_listener;
public:
explicit MyTriggeredWrapper(const FUNC& f,
bslma::Allocator *basicAllocator = 0);
MyTriggeredWrapper(const TYPE& v,
const FUNC& f,
bslma::Allocator *basicAllocator = 0);
MyTriggeredWrapper(const MyTriggeredWrapper& original,
bslma::Allocator *basicAllocator = 0);
~MyTriggeredWrapper()
{
}
MyTriggeredWrapper& operator=(const TYPE& rhs);
MyTriggeredWrapper& operator=(const MyTriggeredWrapper& rhs);
void setValue(const TYPE& value);
const TYPE& value() const
{
return d_value;
}
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 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);
}