RAD Studio
|
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.
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 rererence
The syntax of an rvalue reference is to add && after a type:
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.
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.
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:
// 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 { 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:
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 could 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 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:
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; }
Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
|
What do you think about this topic? Send feedback!
|