BDE 4.14.0 Production release
|
Macros | |
#define | BSLMF_MOVABLEREF_DEDUCE(...) ::BloombergLP::bslmf::MovableRef<__VA_ARGS__> |
Provide a vocabulary type to enable move semantics.
t_TYPE
that is deducibleT&&
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:
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&&
.
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:
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.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.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.
C++11 has two entirely different uses of the notation T&&
:
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.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: There are two sides of move semantics:
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:
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:
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):
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
.
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:
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.
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':
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
:
The first operation actually demonstrating the use of MovableRef<t_TYPE>
is the move constructor:
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
:
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:
To verify that copying of Vector<t_TYPE>
objects works, a copy is created:
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:
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
#define BSLMF_MOVABLEREF_DEDUCE | ( | ... | ) | ::BloombergLP::bslmf::MovableRef<__VA_ARGS__> |