rvalue References (C++11)

From RAD Studio
Jump to: navigation, search

Go Up to C++11 Features in the Classic Compiler

Attention: This page refers to a C++11 feature in the Classic compiler. The Classic compiler is not recommended: instead it is recommend you use the Clang-enhanced compilers, which support modern C++ including C++11, C++14 and C++17.

BCC32 includes the use of rvalue references, which allow creating a reference to temporaries. Also, rvalue references avoid unnecessary copying and make possible perfect forwarding functions. This feature is one of the C++11 features.


Rvalue references are a compound type like standard C++ references, which are referred to as lvalue references. An lvalue reference is formed by appending the ampersand character (&) to a type:

SomeClass l;
SomeClass& lReference = l;    //lvalue reference

The syntax of an rvalue reference is to add && after a type:

SomeClass r;
SomeClass&& rReference = r;    //rvalue reference

An rvalue reference behaves like an lvalue reference, except that you can bind an rvalue reference to a temporary -- an rvalue.

SomeClass a;
a = SomeClass();
SomeClass& lReference = a;         //OK-lvalue reference can bind to an lvalue such as "a"
SomeClass& lReference2 = SomeClass();  //error-lvalue reference can't bind to an rvalue
SomeClass&& rReference = SomeClass();  //OK for rvalue reference to bind to rvalue

// Both references can be used the same way
SomeOtherClass value1 = SomeFunc(lReference);
SomeOtherClass value2 = SomeFunc(rReference);

In the example above, SomeClass() is not bound to an identifier, so it is an rvalue and can be bound to an rvalue reference -- but not an lvalue reference.

rvalue Reference Cannot Bind to a Named lvalue

New rvalue reference rules were set by the C++ specification. The binding rules for rvalue references now work differently in one aspect. In short, an rvalue reference can no longer bind to a named lvalue (except in overload resolution, which has not changed).

The Dinkumware headers still follow the old rules, so you will need to compile with the new option -Vbv if you plan to use the rvalue reference features of the Dinkumware headers.

Eliminating Unnecessary Copying

In many cases, data is copied that simply needs to be moved, that is, the original data holder need not retain the data. An example is swapping data in two structures, so neither structure holds its previous data. It would be logically sufficient to simply switch the references to the data.

Rvalue references can be used to distinguish cases that require copying versus cases that merely require moving data. Since copying can be a lengthy operation, you want to avoid it, if possible.

If a function wants to copy something that has been passed to it as an rvalue, then it can do a move rather than a copy, because it knows that the value is temporary. If a function is passed an lvalue, it may need to do a full copy if copy elision doesn't apply. You can distinguish these cases with the function signature.

Consider a class ExampleClass that has a clone function that makes a deep copy of class instances. You want to define a move function that moves an object's value. This function can be overloaded as follows:

// Parameter is lvalue
ExampleClass move(ExampleClass& l)
    return l.clone();  //returns a copy since we can't touch the original value

// Parameter is rvalue
ExampleClass move(ExampleClass&& r)
    return r;      //returns a reference since we don't care about the temporary's value
template class MyClass<int>;
extern template class MyClass<int>;    // not allowed
extern template class MyClass<float>;  // OK

We could then use the move function for both rvalues and lvalues:

ExampleClass a, b, c;
a = ExampleClass();
b = b.move(a);         //parameter is lvalue
c = c.move(ExampleClass());    //parameter is rvalue

Note that the move function for the rvalue parameter does very little, so it executes much more quickly than the move for an lvalue parameter.

You can use a similar technique for functions that need to make copies, such as copy constructors and assignment operators. Suppose we have a template class with a pointer to some other class, where clone is again a deep copy function:

template <class T>
class PointerClass
    T* pointer;
    // Regular constructor

    // Copy constructor for lvalues
    PointerClass(PointerClass& pcl) : pointer(pcl.pointer ? pcl.pointer.clone() : 0) {}    //make full copy
    // Copy constructor for rvalues
    PointerClass(PointerClass&& pcr) : pointer(pcr.pointer) {pcr.pointer = 0;}

The copy constructor that takes an rvalue:

  • Does a move, not a copy. That is, it simply returns a reference to the data.
  • Treats the rvalue argument pcr just like an lvalue in its code.
  • Leaves the rvalue object in a defined state so that it can safely be deleted.

Non-Copyable but Movable Types

Types that are not copyable, such as ones that use unique_ptr, can be made movable. Although they cannot define assignment operators, they can implement move functions and swap functions, since these do not require copying when rvalue references are used. A sort function could be developed, since it only requires swapping, not copying.

For instance, consider this factory function that takes one argument:

template <class T, class U>
factory(const U& u)
    return new T(u);

The above definition of factory works in this case:

T* p = factory<T>(7);

However, a compiler error occurs when a T is used whose constructor's parameter is a non-const reference. You can fix this case by removing const from the definition:

template <class T, class U>
factory(U& u)
    return new T(u);

However, the previous example now fails:

T* p = factory<T>(7); // compiler error
T* u = new T(7);   //OK

This causes an error, because the value 7 causes the template argument to be matched to int &, but this does not bind to the rvalue 7.

This can be remedied by defining a factory function for each case of const and non-const. This is however problematic, because the number of functions needed increases exponentially with the number of arguments.

If you make the argument an rvalue reference, you can simplify the situation:

template <class T, class U>
factory(u&& u)
   return new T(forward<U>(u));

Now the argument u binds to both rvalues and lvalues. The forward function returns an rvalue or lvalue, exactly as it was passed. It can be defined in this way:

template <class U>
struct identity
    typedef U type;

template <class U>
U&& forward(typename identity<U>::type&& u)
    return u;

See Also