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

Macros

#define BSLMF_MOVABLEREF_DEDUCE(...)    ::BloombergLP::bslmf::MovableRef<__VA_ARGS__>
 

Detailed Description

Outline

Purpose

Provide a vocabulary type to enable move semantics.

Classes

Macros

See also

Description

This component provides a class template, bslmf::MovableRef used to convey the information that an object will not be used anymore so that its representation can be transferred elsewhere. In C++11 terminology an object represented by a bslmf::MovableRef<T> can be moved from. This component also provides a utility struct bslmf::MovableRefUtil that enables use of identical code for C++03 and C++11 to implement move semantics.

An object is /movable/ when it isn't being used in a way depending on its current representation after an operation on this object. For example, when passing a temporary object to a function the temporary object is movable: it can't be referred to other than in the function call. When objects are no longer be used their internal representation can be transferred to another object. Transferring the internal representation of an object to another object is called /moving an object/. The purpose of bslmf::MovableRef<T> is to indicate to a function that an object can be moved to another object.

With a C++11 implementation bslmf::MovableRef<T> is an alias template for T&&. With a C++03 implementation bslmf::MovableRef<T> is a class template providing l-value access to a movable object. The objective of this component is to provide a name for the concept of a movable object. Using a common name enables use of manual move semantics when using C++03. With C++11 additionally automatic move semantics is enabled resulting in moving objects known to the compiler to go out of scope, e.g., when passing a temporary object to a function or returning a local variable.

Using bslmf::MovableRef<T> to support movable types enables the implementation of move semantics that work with both C++03 and C++11 without conditional compilation of the user code. Only the implementation of the component bslmf_movableref uses conditional compilation to select the appropriate implementation choice. For a C++11 implementation the use of bslmf::MovableRef<T> instead of T&& has the advantage that the T will not be deduced an the argument is known to be movable: when a function takes a T&& as argument with a deduced T the deduced type may be an l-value reference that isn't necessarily movable. When using bslmf::MovableRef<T> with C++11 the type will not be deduced and to turn an l-value into a movable object it is necessary to explicitly use move() at the call site.

For consistent use across different versions of the C++ standard, a few utility functions are provided in the utility class bslmf::MovableRefUtil. This class contains functions for moving and accessing objects. To enable an identical notation to access an object with C++11 (where bslmf::MovableRef<T> is just an l-value of type T) and with C++03 where bslmf::MovableRef<T> is a class type referencing an l-value of type T, the function template bslmf::MovableRefUtil::access(r) is provided. Similarly, the function bslmf::MovableRefUtil::move(r) provides identical notation for producing a movable reference in both C++03 and C++11.

In addition to the move and access functions, the bslmf::MovableRefUtil namespace provides 7 metafunctions that closely correspond to similar metafunctions in the C++11 standard library (and which defer to the standard library where available). These 7 metafunctions and their C++11 equivalents are shown in the table below:

+-----------------------------+------------------------------+
| MovableRefUtil trait | C++11 standard trait |
+-----------------------------+------------------------------+
| IsLvalueReference<t_TYPE> | is_lvalue_reference<t_TYPE> |
| IsMovableReference<t_TYPE> | is_rvalue_reference<t_TYPE> |
| IsReference<t_TYPE> | is_reference<t_TYPE> |
| RemoveReference<t_TYPE> | remove_reference<t_TYPE> |
| AddLvalueReference<t_TYPE> | add_lvalue_reference<t_TYPE> |
| AddMovableReference<t_TYPE> | add_rvalue_reference<t_TYPE> |
| Decay<t_TYPE> | decay<t_TYPE> |
+-----------------------------+------------------------------+

Note that volatile-qualified MovableRef objects are non-sensical; unlike const objects they do not occur "naturally" as a result of argument passing or template-argument deduction and there would be no reason for a program to create one on purpose. In C++11, moreover, volatile MovableRef<T> is an alias for T&& volatile, which is not a valid type. The traits above, therefore, will fail to compile when instantiated with a volatile-qualified MovableRef. Note that, although volatile MovableRef<T> doesn't make sense, MovableRef<volatile T> is perfectly fine and are equivalent to volatile T&&.

Use of MovableRef<t_TYPE> Parameters {#bslmf_movableref-use-of-movableref<t_type>-parameters}

There are a number of differences how MovableRef<t_TYPE> parameters are handled between C++03 and C++11 implementations. Due to the language differences there is no way to avoid these. This component enables use of move semantics in both C++03 and C++11 when done right. It doesn't try to make implementation of move semantics easier. Here are some notes to keep in mind when using this component:

  1. When using a t_TYPE&& in a context where t_TYPE is deduced, the resulting reference does normally not refer to an object that can be moved from! If bslmf::MovableRef<t_TYPE> would deduce the type when using a C++11 implementation the name would be rather misleading. Thus, the t_TYPE won't be deduced. When using a C++03 the type /can/ be deduced. However, a program depending on the t_TYPE being deduced from a bslmf::MovableRef<t_TYPE> will not compile with a C++11 implementation.
  2. Returning MovableRef<t_TYPE> (or t_TYPE&&) from a function is almost always wrong. In particular note that the same life-time issues apply to MovableRef<t_TYPE> as they do to references of objects: when returning a reference the object referred to cannot be on the stack, i.e., returning a MovableRef<t_TYPE> referring to a local variable or a by-value function parameter is certainly wrong. Returning a MovableRef<t_TYPE> to a function parameter received as a reference type can be correct.
  3. Using the argument of type MovableRef<t_TYPE> directly in a function typically results in incorrect behavior either when using C++03 or when using C++11. Instead, use these arguments together with MovableRefUtil::move(), MovableRefUtil::access(), or bind them to a non-const l-value reference.

The purpose of access(x) is to use the same notation for member access to x independent on whether it is an actual l-value reference or an MovableRef<t_TYPE>. For a concrete examples assume x is a bsl::pair<A, B>. When using a C++11 implementation MovableRef<bsl::pair<A, B> > is really just a bsl::pair<A, B>&& and the elements could be accessed using x.first and x.second. For a C++03 implementation MovableRef<bsl::pair<A, B> > is a class type and x.first and x.second are not available. Instead, a reference to the pair needs to be obtained that could be done using static_cast<bsl::pair<A, B >&>(x) or by using a named variable. To unify the notation between the C++03 and C++11 implementation, simultaneously simplifying the C++03 use MovableRefUtil::access(x) can be used.

Template Deduction and Argument Forwarding

C++11 has two entirely different uses of the notation T&&:

  1. In contexts where the type T is not deduced T&& indicates an "rvalue reference". The notation implies that the resources held by the referenced object can be reused, typically because the lifetime of the object is about to end. An argument of type T&& can bind to an rvalue of type T or to an lvalue of type T that has been explicitly "moved" by the caller.
  2. In contexts where the type T is deduced T&& indicates a "forwarding reference. The argument can be either an rvalue or an lvalue and the called function can preserve the value category (rvalue or lvalue) when forwarding the reference to another function. The <tt>bslmf::MovableRef\<T\></tt> emulation of <tt>T\&\&</tt> in C++03 works only for rvalue references, i.e., the first use of the notation. The C++11 definition of <tt>bslmf::MovableRef\<T\></tt> is designed specifically to avoid deduction of <tt>T</tt>, thus preventing it from accidentally being used as a forwarding reference (which would have the wrong effect in C++03). For contexts where it is desirable to deduce <tt>T</tt>, the <tt>BSLMF_MOVABLEREF_DEDUCE</tt> macro is provided. When invoked like <tt>BSLMF_MOVABLEREF_DEDUCE(T)</tt>, this macro expands to <tt>bslmf::MovableRef\<T\></tt> in C++03, and a type alias to <tt>T\&\&</tt> for which substitution fails if <tt>T\&\&</tt> would be an lvalue reference in C++11 and later. In both cases, the type <tt>T</tt> is deducible, and substitution succeeds only if <tt>BSLMF_MOVABLEREF_DEDUCE(T)</tt> deduces a movable reference. @subsection bslmf_movableref-usage Usage This section illustrates intended usage of this component. @subsubsection bslmf_movableref-example-1-difference-in-moving-trivial-and-non-trivial-fields Example 1: Difference In Moving Trivial And Non-trivial Fields This example will show the definition of a couple simple move constructor that use <tt>MovableRef</tt>, and highlight the difference in how different data members are handled. First, we create an minimal type similar to @ref string_view . For simplicity, we implement methods we are interested in directly in the class declaration and omit the rest: @code class StringView { // This class provides a view on a C-string. // DATA const char *d_string_p; // pointer to the data public: // CREATORS ... @endcode Here we define the move constructor for <tt>StringView</tt>. Note that the <tt>original</tt> here is either an object of type 'MovableRef<StringView> (in C++03), or an true r-value reference <tt>StringView\&\&</tt>, in C++11 or later. Because the type of <tt>original</tt> may be different when built with different language standards, we must take care to manipulate and access the value in a way that is syntactically valid irrespective of the language-standard being used. Here, we use <tt>MovableRefUtil::access</tt> to obtain <tt>const \&</tt> to <tt>original</tt>, and we assign <tt>original</tt> to an l-value reference (<tt>StringView\&</tt>) to set its value to 0. These are both operations that support the same syntax across language standards: @code StringView(bslmf::MovableRef<StringView> original) // Create a 'StringView' object that refers to the same c-string // as the specified 'original' object, and reset 'original' to not // refer to any string. : d_string_p(bslmf::MovableRefUtil::access(original).d_string_p) { StringView& reference = original; reference.d_string_p = 0; } // ACCESSORS ... }; @endcode Now, we define a second class, <tt>Employee</tt>, that contains both non-trivial <tt>StringView</tt> and a trivial integer field: @code class Employee { // This class represents an employee card. // DATA StringView d_name; // employee name int d_id; // employee id public: // CREATORS ... @endcode Here we define the move constructor for 'Employee". Note that for the data members of original, d_id is a fundamental type and we simply can access the value as a const &, but d_name is a StringView, so that we must use MovableRefUtil::move to move it in a language-standard neutral way:
    Employee(bslmf::MovableRef<Employee> original)
    // Create an 'Employee' object that has the same value as the
    // specified 'original' object, and reset 'original' to the default
    // state.
    : d_name(bslmf::MovableRefUtil::move(
    bslmf::MovableRefUtil::access(original).d_name))
    , d_id(bslmf::MovableRefUtil::access(original).d_id)
    {
    Employee& reference = original;
    reference.d_id = 0;
    }
    // ACCESSORS
    ...
    };
    Definition bslmf_movableref.h:751
    Definition bdlbb_blob.h:576

Example 2: Basic MovableRef<T> Usage {#bslmf_movableref-example-2-basic-movableref-usage}

There are two sides of move semantics:

  1. Classes or class templates that are move-enabled, i.e., which can transfer their internal representation to another object in some situations. To become move-enabled a class needs to implement, at least, a move constructor. It should probably also implement a move assignment operator.
  2. Users of a potentially move-enabled class may take advantage of moving objects by explicitly indicating that ownership of resources may be transferred. When using C++11 the compiler can automatically detect some situations where it is safe to move objects but this features is not available with C++03.

The usage example below demonstrate both use cases using a simplified version of std::Vector<T>. The class template is simplified to concentrate on the aspects relevant to bslmf::MovableRef<T>. Most of the operations are just normal implementations to create a container. The last two operations described are using move operations.

Assume we want to implement a class template similar to the standard library vector facility. First we declare the class template Vector<t_TYPE>. The definition of the this class template is rather straight forward, and for simplicity a few trivial operations are implemented directly in the class definition:

template <class t_TYPE>
class Vector
{
t_TYPE *d_begin;
t_TYPE *d_end;
t_TYPE *d_endBuffer;
static void swap(t_TYPE*& a, t_TYPE*& b);
// Swap the specified pointers 'a' and 'b'.
public:
Vector();
// Create an empty Vector.
Vector(bslmf::MovableRef<Vector> other); // IMPLICIT
// Create a Vector by transferring the content of the specified
// 'other'.
Vector(const Vector& other);
// Create a Vector by copying the content of the specified 'other'.
Vector& operator= (Vector other);
// Assign a Vector by copying the content of the specified 'other'
// and return a reference to this object. Note that 'other' is
// passed by value to have the copy or move already be done, or
// even elided. Within the body of the assignment operator the
// content of 'this' and 'other' are simply swapped.
~Vector();
// Destroy the Vector's elements and release any allocated memory.
t_TYPE& operator[](int index) { return this->d_begin[index]; }
// Return a reference to the object at the specified 'index'.
const t_TYPE& operator[](int index) const
{ return this->d_begin[index]; }
// Return a reference to the object at the specified 'index'.
t_TYPE *begin() { return this->d_begin; }
// Return a pointer to the first element.
const t_TYPE *begin() const { return this->d_begin; }
// Return a pointer to the first element.
int capacity() const { return int(this->d_endBuffer - this->d_begin); }
// Return the capacity of the Vector.
bool empty() const { return this->d_begin == this->d_end; }
// Return 'true' if the Vector is empty and 'false' otherwise.
t_TYPE *end() { return this->d_end; }
// Return a pointer to the end of the range.
const t_TYPE *end() const { return this->d_end; }
// Return a pointer to the end of the range.
void push_back(const t_TYPE& value);
// Append a copy of the specified 'value' to the Vector.
void push_back(bslmf::MovableRef<t_TYPE> value);
// Append an object moving the specified 'value' to the new
// location.
void reserve(int newCapacity);
// Reserve enough capacity to fit at least as many elements as
// specified by 'newCapacity'.
int size() const { return int(this->d_end - this->d_begin); }
// Return the size of the object.
void swap(Vector& other);
// Swap the content of the Vector with the specified 'other'.
};
void swap(OptionValue &a, OptionValue &b)
bsl::size_t size(const TYPE &array)
Return the number of elements in the specified array.
T::iterator begin(T &container)
Definition bslstl_iterator.h:1495
T::iterator end(T &container)
Definition bslstl_iterator.h:1523
BSLS_KEYWORD_CONSTEXPR bool empty(const CONTAINER &container)
Definition bslstl_iterator.h:1279

The class stores pointers to the begin and the end of the elements as well as a pointer to the end of the allocated buffer. If there are no elements, null pointers are stored. There a number of accessors similar to the accessors used by std::Vector<t_TYPE>.

The default constructor creates an empty Vector<t_TYPE> by simply initializing all member pointers to be null pointers:

template <class t_TYPE>
Vector<t_TYPE>::Vector()
: d_begin()
, d_end()
, d_endBuffer()
{
}

To leverage already implemented functionality some of the member functions operate on a temporary Vector<t_TYPE> and move the result into place using the swap() member function that simply does a memberwise swap() (the function swapping pointers is implemented here to avoid any dependency on functions defined in another level):

template <class t_TYPE>
void Vector<t_TYPE>::swap(t_TYPE*& a, t_TYPE*& b)
{
t_TYPE *tmp = a;
a = b;
b = tmp;
}
template <class t_TYPE>
void Vector<t_TYPE>::swap(Vector& other)
{
this->swap(this->d_begin, other.d_begin);
this->swap(this->d_end, other.d_end);
this->swap(this->d_endBuffer, other.d_endBuffer);
}

The member function reserve() arranges for the Vector<t_TYPE> to have enough capacity for the number of elements specified as argument. The function first creates an empty Vector<t_TYPE> called tmp and sets tmp up to have enough capacity by allocating sufficient memory and assigning the different members to point to the allocated buffer. The function then iterates over the elements of this and for each element it constructs a new element in tmp.

template <class t_TYPE>
void Vector<t_TYPE>::reserve(int newCapacity)
{
if (this->capacity() < newCapacity) {
Vector tmp;
int size = int(sizeof(t_TYPE) * newCapacity);
tmp.d_begin = static_cast<t_TYPE*>(operator new(size));
tmp.d_end = tmp.d_begin;
tmp.d_endBuffer = tmp.d_begin + newCapacity;
for (t_TYPE* it = this->d_begin; it != this->d_end; ++it) {
new (tmp.d_end) t_TYPE(*it);
++tmp.d_end;
}
this->swap(tmp);
}
}

Any allocated data and constructed elements need to be release in the destructor. The destructor does so by calling the destructor of the elements in the buffer from back to front. Once the elements are destroyed the buffer is released:

template <class t_TYPE>
Vector<t_TYPE>::~Vector()
{
if (this->d_begin) {
while (this->d_begin != this->d_end) {
--this->d_end;
this->d_end->~t_TYPE();
}
operator delete(this->d_begin);
}
}

Using reserve() and constructing the elements it is straight forward to implement the copy constructor. First the member pointers are initialed to null. If other is empty there is nothing further to do as it is desirable to not allocate a buffer for an empty Vector. If there are elements to copy the buffer is set up by calling reserve() to create sufficient capacity. Once that is done elements are copied by iterating over the elements of other and constructing elements using placement new in the appropriate location.

template <class t_TYPE>
Vector<t_TYPE>::Vector(const Vector& other)
: d_begin()
, d_end()
, d_endBuffer()
{
if (!other.empty()) {
this->reserve(4 < other.size()? other.size(): 4);
assert(other.size() <= this->capacity());
for (t_TYPE* it = other.d_begin; it != other.d_end; ++it) {
new (this->d_end) t_TYPE(*it);
++this->d_end;
}
}
}

A simple copy assignment operator can be implemented in terms of copy/move constructors, swap(), and destructor (in a real implementation the copy assignment would probably try to use already allocated objects). In this implementation that argument is taken by value, i.e., the argument is already constructed using copy or move construction (which may have been elided), the content of this is swapped with the content of other leaving this in the desired state, and the destructor will release the former representation of this when other is destroyed':

template <class t_TYPE>
Vector<t_TYPE>& Vector<t_TYPE>::operator= (Vector other)
{
this->swap(other);
return *this;
}

To complete the normal C++03 operations of Vector<t_TYPE> the only remaining member function is push_back(). This function calls reserve() to obtain more capacity if the current capacity is filled and then constructs the new element at the location pointed to by d_end:

template <class t_TYPE>
void Vector<t_TYPE>::push_back(const t_TYPE& value)
{
if (this->d_end == this->d_endBuffer) {
this->reserve(this->size()? 2 * this->size() : 4);
}
assert(this->d_end != this->d_endBuffer);
new(this->d_end) t_TYPE(value);
++this->d_end;
}

The first operation actually demonstrating the use of MovableRef<t_TYPE> is the move constructor:

template <class t_TYPE>
Vector<t_TYPE>::Vector(bslmf::MovableRef<Vector> other)
: d_begin(bslmf::MovableRefUtil::access(other).d_begin)
, d_end(bslmf::MovableRefUtil::access(other).d_end)
, d_endBuffer(bslmf::MovableRefUtil::access(other).d_endBuffer)
{
Vector& reference(other);
reference.d_begin = 0;
reference.d_end = 0;
reference.d_endBuffer = 0;
}

This constructor gets an MovableRef<Vector<t_TYPE> > passed as argument that indicates that the referenced objects can be modified as long as it is left in a state meeting the class invariants. The implementation of this constructor first copies the d_begin, d_end, and d_capacity members of other. Since other is either an object of type MovableRef<Vector<t_TYPE> > (when compiling using a C++03 compiler) or an r-value reference Vector<t_TYPE>&& the members are accessed using MovableRefUtil::access(other) to get a reference to a Vector<t_TYPE>. Within the body of the constructor an l-value reference is obtained either via the conversion operator of MovableRef<T> or directly as other is just an l-value when compiling with a C++11 compiler. This reference is used to set the pointer members of the object referenced by other to 0 completing the move of the content to the object under construction.

Finally, a move version of push_back() is provided: it takes an MovableRef<t_TYPE> as argument. The type of this argument indicates that the state can be transferred and after arranging enough capacity in the Vector<t_TYPE> object a new element is move constructed at the position d_end:

template <class t_TYPE>
void Vector<t_TYPE>::push_back(bslmf::MovableRef<t_TYPE> value)
{
if (this->d_end == this->d_endBuffer) {
this->reserve(this->size()? int(1.5 * this->size()): 4);
}
assert(this->d_end != this->d_endBuffer);
new(this->d_end) t_TYPE(bslmf::MovableRefUtil::move(value));
++this->d_end;
}
static MovableRef< t_TYPE > move(t_TYPE &reference) BSLS_KEYWORD_NOEXCEPT
Definition bslmf_movableref.h:1060

Note that this implementation of push_back() uses bslmf::MovableRefUtil::move(value) to move the argument. For a C++03 implementation the argument would be moved even when using value directly because the type of value stays bslmf::MovableRef<t_TYPE>. However, for a C++11 implementation the argument value is an l-value and using it directly would result in a copy.

To demonstrate the newly created Vector<t_TYPE> class in action, first a Vector<int> is created and filled with a few elements:

Vector<int> vector0;
for (int i = 0; i != 5; ++i) {
vector0.push_back(i);
}
for (int i = 0; i != 5; ++i) {
assert(vector0[i] == i);
}

To verify that copying of Vector<t_TYPE> objects works, a copy is created:

Vector<int> vector1(vector0);
assert(vector1.size() == 5);
assert(vector1.size() == vector0.size());
for (int i = 0; i != vector1.size(); ++i) {
assert(vector1[i] == i);
assert(vector1[i] == vector0[i]);
}

When using moving this vector0 to a new location the representation of the new object should use the original begin():

const int *first = vector0.begin();
Vector<int> vector2(bslmf::MovableRefUtil::move(vector0));
assert(first == vector2.begin());

When create a Vector<Vector<int> > and using push_back() on this object with vector2 a copy should be inserted:

Vector<Vector<int> > vVector;
vVector.push_back(vector2); // copy
assert(vector2.size() == 5);
assert(vVector.size() == 1);
assert(vVector[0].size() == vector2.size());
assert(vVector[0].begin() != first);
for (int i = 0; i != 5; ++i) {
assert(vVector[0][i] == i);
assert(vector2[i] == i);
}

When adding another element by moving vector2 the begin() of the newly inserted element will be the same as first, i.e., the representation is transferred:

vVector.push_back(bslmf::MovableRefUtil::move(vector2)); // move
assert(vVector.size() == 2);
assert(vVector[1].begin() == first);
assert(vVector[1].size() == 5);

Compiling this code with both C++03 and C++11 compilers shows that there is no need for conditional compilation in when using MovableRef<t_TYPE> while

move semantics is enabled in both modes.

Macro Definition Documentation

◆ BSLMF_MOVABLEREF_DEDUCE

#define BSLMF_MOVABLEREF_DEDUCE (   ...)     ::BloombergLP::bslmf::MovableRef<__VA_ARGS__>