RAD Studio |  |

rvalue References (C++0x)
Description | Eliminating Unnecessary Copying | Non-Copyable but Movable Types | See Also
Collapse All

C++Builder 2009 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++0x features.

Description

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:

Copy Code

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

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

Copy Code

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

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

Copy Code

    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 identifiier, so it is an rvalue and can be bound to an rvalue reference -- but not an lvalue reference.

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 rererences 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:

Copy Code

    // 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
    }

Copy Code

    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:

Copy Code

    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:

Copy Code

    template <class T>
    class PointerClass
    {
    private:
        T* pointer;
    public:
        // Regular constructor
        PointerClass(void);

        // 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:

Copy Code

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

The above definition of factory works in this case:

Copy Code

    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 could fix this case by removing const from the definition:

Copy Code

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

However, the previous example now fails:

Copy Code

    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 could be remedied by defining a factory function for each case of const and non-const. This is 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:

Copy Code

    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:

Copy Code

    template <class U>
    struct identity
    {
        typedef U type;
    };

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

See Also

C++0x Features (C++Builder 2009)

A Proposal to Add an Rvalue Reference to the C++ Language

A Brief Introduction to Rvalue References

Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!