Provide a vocabulary type to enable move semantics.
More...
Namespaces |
namespace | bslmf |
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:
- 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:
-
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.
-
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.
-
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&&
:
-
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.
-
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
bslmf::MovableRef<T>
emulation of T&&
in C++03 works only for rvalue references, i.e., the first use of the notation. The C++11 definition of bslmf::MovableRef<T>
is designed specifically to avoid deduction of T
, 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
T
, the BSLMF_MOVABLEREF_DEDUCE
macro is provided. When invoked like BSLMF_MOVABLEREF_DEDUCE(T)
, this macro expands to bslmf::MovableRef<T>
in C++03, and a type alias to T&&
for which substitution fails if T&&
would be an lvalue reference in C++11 and later. In both cases, the type T
is deducible, and substitution succeeds only if BSLMF_MOVABLEREF_DEDUCE(T)
deduces a movable reference.
-
- Usage:
- There are two sides of move semantics:
-
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.
-
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);
public:
Vector();
Vector(bslmf::MovableRef<Vector> other);
Vector(const Vector& other);
Vector& operator= (Vector other);
~Vector();
t_TYPE& operator[](int index) { return this->d_begin[index]; }
const t_TYPE& operator[](int index) const
{ return this->d_begin[index]; }
t_TYPE *begin() { return this->d_begin; }
const t_TYPE *begin() const { return this->d_begin; }
int capacity() const { return int(this->d_endBuffer - this->d_begin); }
bool empty() const { return this->d_begin == this->d_end; }
t_TYPE *end() { return this->d_end; }
const t_TYPE *end() const { return this->d_end; }
void push_back(const t_TYPE& value);
void push_back(bslmf::MovableRef<t_TYPE> value);
void reserve(int newCapacity);
int size() const { return int(this->d_end - this->d_begin); }
void swap(Vector& other);
};
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;
}
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()
: 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);
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: 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. ----------------------------------------------------------------------------