Archive

Archive for December, 2010

Programmer’s Corner: A Rationale for C++0x’s Rvalue-References

December 18th, 2010 No comments

Arguably one of the more advanced features of the upcoming C++0x standard are the so-called rvalue-references. A few months ago, we were discussing an example of what unpleasant issues one can run into in a world without rvalue-references. In this article, we will try to give a more extensive rationale for why we need another type of references in C++ and how they are specified in the current draft of the standard.

Why References?

When it comes to passing arguments, most modern programming languages already made the choice for you: Built-in types are passed by value, complex types by reference. In C++, things are more complicated, as a programmer may choose between passing by-value, by-pointer or by-reference.

The first two are natural heritage from C. References may seem kind of redundant at a first glance, since they  behave almost exactly like (const) pointers from a semantic point of view. And indeed the initial motivation for references came from a purely syntactic problem, namely that of operator overloading for complex types:

class MyType {
 ...
public:
  MyType operator+(MyType& rhs) const;
};
 
MyType a, b, c;
a = b + c;

This natural way of using operators can not be achieved with pointers alone (which would require explicit address operators on the caller’s side) . While there are further differences between pointers and references, this remains the most prominent example of why the low-level concept of raw-memory pointers is no longer sufficient in the context of higher-level C++ code.

References and Rvalues

However, the initial implementation of References had a severe problem, as it allowed binding to rvalues:

// side-effect: increments the passed variable
void increment(long& l) {
   ++l;
}
 
[...]
int i = 41;
increment(i);
//expected: i == 42

This is actually a very subtle error. Since C++ inherited the complex integer conversion rules from C, the compiler may easily resolve the call to increment(), by implicitly casting the int to long, creating an unnamed temporary (i.e. an rvalue) in the process. However, all side effects produced by the function call now get applied to that unnamed temporary instead of the initial int, so the function really has no effect on the int at all.

In order to avoid this pitfall while maintaining the powerful syntax of references, the 2.0 release of C++ from 1989 prohibited binding of non-const references to rvalues. This is the behavior that is still in use today and this is also the behavior that caused trouble in the previous article.

Introducing: Rvalue-References

In C++98, we distinguished between pointers (raw memory addresses), references (alias for a named value) and the special case of const-references (same as reference but may bind to an unnamed value as well).

As it turns out, it is indeed required to introduce another type of references, a non-const reference that binds to unnamed temporaries and unnamed temporaries only: Rvalue-references. Actually, the initial draft allowed rvalue-references to bind to lvalues as well, but that turned out to cause more trouble than good, so by the current draft, rvalue-references may indeed only bind to rvalues.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyType {
public:
  /* Constructor
   */
  MyType(int dummy);
  /* Copy constructor; uses const-lvalue-reference
   */
  MyType(MyType const& t);
  /* Move constructor; uses rvalue-references (new in C++0x)
   */
  MyType(MyType&& t);
};
 
int main()
{
    MyType a(42);   //construction
    MyType a_copy(a);  //copy construction
    MyType a_moved(MyType(42));  //move construction from rvalue
}

Before we take a look at some of the actual use-cases for this feature, we need to cover one subtle yet very important detail: Although an rvalue-reference ever only binds to rvalues, it is itself an ordinary lvalue as long as its named. Consider the following function:

void take_by_rvalref(MyType&& p)
{
    MyType p_copy(p);  // *no* move construction!
}

Although the parameter is passed as an rvalue-reference, p_copy is constructed using the copy-constructor, since p is itself an lvalue. This is similar to how a const reference always behaves like a (const) lvalue, even if it was bound to an rvalue by the caller.

In certain situations, it may be desirable to treat p as an rvalue to enforce invocation of the move-constructor, which we will cover below.

Move Semantics

With rvalue references, we can resolve the problem from our last article. Assuming we have an object of class Guard that is responsible for freeing a resource upon destruction, we ran into trouble when trying to write a factory method that returns resources encapsulated by Guards:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Guard {
private:
	int resource_;
public:
	Guard(int r)
		:resource_(r)
	{
		::std::cout << this << " Guard Constructor" << ::std::endl;
		::std::cout << this << " *** Guarding " << resource_ << ::std::endl;
	}
	~Guard()
	{
		::std::cout << this << " Guard Destructor" << ::std::endl;
		if(resource_ > 0) {
			::std::cout << this << " *** Free " << resource_ << ::std::endl;
		}
	}
	Guard(Guard& rhs)
		:resource_(rhs.Detach())
	{
		::std::cout << this << " Copy constructor; Assuming ownership of " 
					<< resource_ << ::std::endl;
	}
private:
	int Detach()
	{
		::std::cout << this << " Detaching " << resource_ << ::std::endl;
		int ret = resource_;
		resource_ = 0;
		return ret;
	}
};
 
class Factory {
public:
	Guard BuildGuard()
	{
		return Guard(42);
		//error: can not copy-construct from unnamed temporary Guard
	}
};

With rvalue-references, we resolve the issue by introducing a move constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Guard {
	[...]
public:
	Guard(Guard&& rhs)
		:resource_(rhs.Detach())
	{
		::std::cout << this << " Guard Move Constructor" << ::std::endl;
		::std::cout << " Assuming ownership of " << resource_ << ::std::endl;
	}
};
 
class Factory {
public:
	Guard BuildGuard()
	{
		return Guard(42);
		//fine: invokes move-constructor Guard(Guard&&)
	}
};

Actually, on both VC2010 and gcc 4.3.4 this makes RVO kick in, so you probably won’t see the output from the move-constructor on the console.

Another use for move semantics is the elimination of spurious copies. Let’s assume for a moment that copy constructing a Guard would be horribly expensive, so things like the following become undesirable:

1
2
3
4
5
6
7
8
9
10
Guard BuildGuard()
{
	Guard g(42);
 
	//do some fancy stuff with g
	// [...]
 
	return g;
	//bad: implicit copy construction
}

Unless we get lucky with the compiler, we will have to pay for the copy-construction here. This is very annyoing, especially regarding that the local source of the copy will be destroyed immediately after the copying is finished. In C++98 the usual workaround for this would be to pass a Guard* as output argument to the function. However, no one likes output arguments, as they are clumsy to use and make your code hard to read.
In this case it would be nice to force the usage of the move constructor. Since moving allows to change the state of the source argument, it can be implemented very efficiently. For this case C++0x offers the std::move() function which converts its lvalue argument to an rvalue:

1
2
3
4
5
6
7
8
9
10
Guard BuildGuard()
{
	Guard g(42);
 
	//do some fancy stuff with g
	// [...]
 
	return ::std::move(g);
	//force move construction
}

Obviously, std::move() should only be used with extreme caution. It’s best to think of it as a special form of typecast that invalidates its source argument.

A nice property of move-semantics is, that we may omit the copy-constructor of an object altogether (i.e. specify a privately declared, unimplemented copy-constructor), creating types that are movable, but not copyable.

Perfect Forwarding

The last use is perhaps the most subtle one. Most C++-programmers will probably never need to make use of this feature directly, so feel free to skip this section if your head is already spinning with confusion.

Let’s return to our factory example once more and assume that Guard is to be constructed from some other object:

1
2
3
4
5
6
7
8
template<typename T>
Guard BuildGuard(T const& obj)
{
	Guard g(obj);
	// binds only to Guard(T const&)
	// [...]
	return ::std::move(g);
}

This works fine as long as the constructor of Guard is expecting a T const&. If for some reason it only accepts a non-const reference, we need to once again fall back to rvalue-references to allow passing of unnamed temporaries to BuildGuard():

1
2
3
4
5
6
7
8
template<typename T>
Guard BuildGuard(T&& o)
{
	Guard g(o);
	// also binds to Guard(T& o)
	// [...]
	return ::std::move(g);
}

The obvious disadvantage here is that the factory method now no longer works with lvalue arguments, at least not without an explicit std::move() on the caller’s side.
The only way to make it work for both cases is to implement two factory methods, which will result in an exponential interface blowup once the number of arguments increases.
Usually, this is not a problem, as Guard is likely to support only one of the two constructor types and we know beforehand which one that is going to be. Things only get ugly when Guard itself is a templated type, so we can not make any assumptions about its interface.
In order to handle this case, we need a way to preserve the const-ness of the argument bound to the rvalue-reference. This is taken care of by the std::forward() function-template:

1
2
3
4
5
6
7
8
9
template<typename T>
Guard BuildGuard(T&& o)
{
	Guard g(::std::forward<T>(o));
	// binds to either Guard(T&) or Guard(T const&),
	// depending on the type of the value bound by o
	// [...]
	return ::std::move(g);
}

One interesting side note about std::forward is that it only works with templated parameters: If the function is passed an unnamed temporary of type Foo, T resolves to Foo; if it is however passed an lvalue of type Foo, T resolves to Foo&! So the factory method in this case is actually passed (hold your breath!) an rvalue-reference to an unnamed temporary of type non-const lvalue-reference to Foo.
The std::forward function depends on this behavior of the template type deduction system to work correctly.

Conclusion

In this article, we discussed some of the decisions that determined the way references in C++ work today and gave a motivation for a new type of rvalue-references. We also gave a quick overview over some prominent uses for this new feature.
To conclude, let us take a quick look at what the most important benefits of rvalue-references are: First, they allow the implementation of movable types, which takes a lot of scare out of auto_ptr-like types. Also, move constructors eliminate a number of causes for spurious copies.
The main target audience for this feature are undoubtedly library developers. Even if you don’t change a single line of code, recompiling code that makes heavy use of the STL on an implementation that fully supports rvalue-references may result in a notable performance boost.

Literature:
Stroustrup, BjarneThe Design and Evolution of C++ (Addison Wesley)
Hinnant, Howard E.; Stroustrup, Bjarne; Kozicki, BronekA Brief Introduction to Rvalue References (N2027)

Categories: Programming