|
BDE 4.14.0 Production release
|
Provide methods to construct arbitrarily-typed objects uniformly.
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:
bslma::Allocator * is treated as interchangeable with bsl::allocator.int or void *) instead of an allocator to indicate that no allocator should be supplied to the object constructor, even if it is AA.std functions will ignore the allocator if it is incompatible with the object type being constructed. The methods in this component, conversely, will ignore the allocator only for non-AA and stl-AA types. For legacy-AA, bsl-AA, and pmr-AA types, an incompatible allocator will result in a compilation failure. This special case exists to avoid subtle errors in the Bloomberg code base where a bad allocator argument is used in a situation where allocator propagation is expected.std::pair.The facilities in this component query several type traits of the TYPE being constructed, using that information as follows:
This section illustrates intended use of this component.
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:
Then, we create a container class that holds a single element and uses bsl::allocator to supply memory:
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:
Now, the destructor destroys the object and deallocates the memory used to hold the element using the allocator:
Next, the assignment operator needs to assign the value without modifying the allocator.
Finally, we perform a simple test of MyContainer, instantiating it with element type int:
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:
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:
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:
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:
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:
Then, we implement setValue, which calls the listener after modifying the 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: