summaryrefslogtreecommitdiff
path: root/chromium/docs/website/site/rvalue-references/index.md
blob: 8a7edb7a58bc3f021c3a4b3566ce7620b49e81ce (plain)
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
---
breadcrumbs: []
page_name: rvalue-references
title: Rvalue references in Chromium
---

Now that C++11 support is available in our standard library (libc++), we should
be able to make use of standard container types in more places, and to use
containers and standard types more efficiently. In order to do that we need to
understand and make use of std::move() and rvalue references.

We hope that the information here will be enough for you to start using these
C++11 features safely in Chromium. (Some of the information here may
purposefully gloss over details or edge cases.)

[TOC]

## Rvalue references and std::move()

The [google style
guide](https://google.github.io/styleguide/cppguide.html#Rvalue_references)
allows the following: Use rvalue references only to define move constructors and
move assignment operators, or for perfect forwarding. Here we talk about the
first type of use, and follow up about the second later.

Many of the ideas here come from a talk given by Scott Meyers that is [available
on
MSDN](https://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler).
The video is really excellent and worth watching.

There are links to full example code with each of the examples. You can try them
out locally, or drop them into an [online
compiler](http://coliru.stacked-crooked.com/).

### 1. What makes rvalues.

You know that you have an rvalue when the object has no name. Typically rvalues
are temporary objects that exist on the stack as the result of a function call
or other operation.

- Returning a value from a function will turn that value into an rvalue. Once
you call return on an object, the name of the object does not exist anymore (it
goes out of scope), so it becomes an rvalue.

- Similarly, calling a function will give back an rvalue. The return value of a
function has no name.

MyType MakeMyType() {
MyType type;
return type;
}

int main(int argc, char \*\*argv) {
// MakeMyType returns an rvalue MyType.
MyType type = MakeMyType();
ProcessMyType(MakeMyType());
}

<http://pastebin.com/raw.php?i=ki3q2Gbi>

### 2. Move constructor is used with rvalues.

Move constructors are similar to copy constructors. They construct an object out
of another object of the same type. The signature of the method changes in order
for it to accept rvalues only.

Here’s an example of a class with a move constructor. It will cause the compiler
to not generate an implicit copy constructor, making this class move-only.

class MyType {
public:
MyType() {}
MyType(MyType&& other) { fprintf(stderr, "move ctor\\n"); }
// Copy constructor is implicitly deleted.
// MyType(const MyType& other) = delete;
};

MyType MakeMyType(int a) {
// This is here to circumvent some compiler optimizations,
// to ensure that we will actually call a move constructor.
if (a % 2) {
MyType type;
return type;
}
MyType type;
return type;
}

int main(int argc, char \*\*argv) {
// MakeMyType returns an rvalue MyType.
// Both lines below call our move constructor.
MyType type = MakeMyType(2);
ProcessMyType(MakeMyType(2));
}

<http://pastebin.com/raw.php?i=tjxdwQuL>

The argument of the move constructor is a MyType&& which is a reference (like a
MyType&) except that it will only bind to an rvalue. That means the following
won’t compile, since it tries to construct a new MyType without an rvalue.

class MyType {
public:
MyType() {}
MyType(MyType&& other) { fprintf(stderr, "move ctor\\n"); }
// Copy constructor is implicitly deleted.
// MyType(const MyType& other) = delete;
};
int main(int argc, char\*\* argv) {
MyType type;
// |type| is an lvalue, so this will not compile because move

// constructor's argument will only bind to rvalues.
MyType other_type = type;
}

<http://pastebin.com/raw.php?i=9bjPNiWY>

### 3. Move constructor does a shallow copy.

A move constructor (like a copy constructor) works by copying the members of
another object into itself. The differences of a move constructor are the
following:

- The move constructor can be destructive. Since the argument is a non-const
reference, it may modify the object passed to it. This is okay since the
argument is an rvalue, so the caller does not hold onto a name for the original
object, and won’t make use of it (note that its destructor will happen though).

- The move constructor only needs to shallow copy. For example if the object
holds a pointer, it doesn’t need to deref the pointer and copy its contents. The
move constructor can just copy the pointer’s address. This is where the
performance benefits of move constructors happen.

class MyType {
public:
MyType() {
pointer_ = new int;
\*pointer_ = 1;
memset(array_, 0, sizeof(array_));
vector_.push_back(3.14);
}
MyType(MyType&& other) {
fprintf(stderr, "move ctor\\n");
// Steal the memory, null out |other|.
pointer_ = other.pointer_;
other.pointer_ = nullptr;
// Copy the contents of the array.
memcpy(array_, other.array_, sizeof(array_));
// Swap with our (empty) vector.
vector_.swap(other.vector_);
}
~MyType() {
delete pointer_;
}
private:
int\* pointer_;
char array_\[42\];
std::vector&lt;float&gt; vector_;
};

int main(int argc, char \*\*argv) {
// MakeMyType returns an rvalue MyType.
// Both lines below call our move constructor.
MyType type = MakeMyType(2);
ProcessMyType(MakeMyType(2));
}

<http://pastebin.com/raw.php?i=TXZB61Sg>

### 4. std::move() will cast a variable to an rvalue.

If you want to construct a new object from a variable, you can still use the
move constructor to do so.
[std::move()](http://en.cppreference.com/w/cpp/utility/move) will cast your
variable to an rvalue, allowing it to bind to a move constructor.

int main(int argc, char \*\*argv) {
MyType type;
// MyType other_type = type; won't work, since there is no copy ctor.
// std::move casts |type| to an rvalue, so it uses the move

// constructor.
MyType other_type = std::move(type);
}

<http://pastebin.com/raw.php?i=DzNMxt5j>

This also means that you can pass an rvalue to a function that uses
pass-by-value.

int main(int argc, char \*\*argv) {
MyType type;

// The return value of MakeMyType is an rvalue, so the move constructor

// is used.

ProcessMyType(MakeMyType(2));

// Error: |type| is an lvalue, and is not copyable.

ProcessMyType(type);

// But std::move() turns lvalues into rvalues, so the move constructor

// is used again.

ProcessMyType(std::move(type));

}

<http://pastebin.com/raw.php?i=P7prZEur>

However..

### 5. You must not use a variable after calling std::move() on it.

Since you have casted your variable to an rvalue, functions that receive rvalues
may act destructively on your variable, so using the variable’s contents
afterward may result in undefined behaviour. The only valid things you may do
after calling std::move() on a variable are:

- Destroy it

- Assign to it (ie. replace its contents)

This applies to member variables too, don't leave your class in an undefined
state when leaving a method.

### 6. std::move() does not move, it casts.

You may think of std::move&lt;T&gt;() as instead rvalue_cast&lt;T&gt;(). It does
not perform any action of its own on the value passed to it, only changing what
methods you can pass it to.

So the following does nothing:

MyType t;

std::move(t); // Casts it but it’s unused.

Just the same as:

char t;

reinterpret_cast&lt;unsigned char&gt;(t); // Cast it but it’s unused.

In fact, the actual implementation of std::move() is done with static_cast. It
is simply a cast from MyType to MyType&& (a reference to an rvalue of MyType):

return static_cast&lt;typename std::remove_reference&lt;T&gt;::type&&&gt;(t);

### 7. Copy constructors can also bind to rvalues (with lower priority).

Never read std::move() on an unfamiliar type and assume anything about what is
going to happen!

If you see code that calls std::move() it looks like it should be calling a move
constructor, but you could be wrong.

When a move constructor on the type being cast to an rvalue does not exist, the
compiler will fall back to binding the rvalue as a const-reference. This means
the copy-constructor would be used instead.

class MyType {
public:
MyType() {
pointer_ = new int;
\*pointer_ = 1;
memset(array_, 0, sizeof(array_));
vector_.push_back(3.14);
}
MyType(const MyType& other) {
fprintf(stderr, "copy ctor\\n");
// Copy memory.
pointer_ = new int;
\*pointer_ = \*other.pointer_;
// Copy the contents of the array.
memcpy(array_, other.array_, sizeof(array_));
// More copies.
vector_ = other.vector_;
}
~MyType() {
delete pointer_;
}
private:
int\* pointer_;
char array_\[42\];
std::vector&lt;float&gt; vector_;
};

int main(int argc, char\*\* argv) {
{
MyType type;
// MyType other_type = type; works the same way here.
// std::move casts |type| to an rvalue, but since there is no move
// constructor, the copy constructor is used.
MyType other_type = std::move(type);
}
{
MyType type;
// MakeMyType returns an rvalue, but since there is no move

// constructor, the copy constructor is used.
ProcessMyType(MakeMyType(2));
// ProcessMyType(type); works the same way here.
// std::move casts |type| to an rvalue, but since there is no move
// constructor, the copy constructor is used.
ProcessMyType(std::move(type));
}
}
<http://pastebin.com/raw.php?i=p5ciyVVk>

So do not assume anything from reading std::move(). You need to understand the
class being moved to know what will happen.

### 8. Move constructors will only bind to non-const rvalues.

Beware of const! Const variables can not be moved from, since move constructors
take a non-const (rvalue) reference. However the compiler will silently
fall-back to the copy constructor.

int main(int argc, char\*\* argv) {
const MyType type = MakeMyType(2);
// std::move casts to an rvalue, but since this is const, it becomes an

// rvalue to const, which uses the copy constructor, not the move

// constructor.
ProcessMyType(std::move(type));
}

<http://pastebin.com/raw.php?i=5aVRz5SQ>

This type of error can only be caught by code inspection (and performance
testing). Always look at the type of the variable on which you call move().

This won’t bite you with function return rvalues, but requires caution when
using std::move().

### 9. Move constructors should be accompanied by move-assignment operators.

Just as a copy constructor MyType(const MyType& t) should be accompanied by a
copy-assignment operator operator=(const MyType& t), so should a move
constructor.

Move-assignment operators work and look the same as a Move constructor. Most
things we’ve discussed about move constructors (behaviour and performance) will
apply in all the same ways to move-assignment.

In following example, the class defines its move constructor in terms of
operator=, using a move assignment to construct itself.

class MyType {
public:
MyType() {
pointer_ = new int;
\*pointer_ = 1;
memset(array_, 0, sizeof(array_));
vector_.push_back(3.14);
}
MyType(MyType&& other) : pointer_(nullptr) {

// Note that although the type of |other| is an rvalue reference,

// |other| itself is an lvalue, since it is a named object. In order

// to ensure that the move assignment is used, we have to explicitly

// specify std::move(other).
\*this = std::move(other);
}
~MyType() {
delete pointer_;
}

MyType& operator=(MyType&& other) {
// Steal the memory, null out |other|.

delete pointer_;

pointer_ = other.pointer_;
other.pointer_ = nullptr;

// Copy the contents of the array.
memcpy(array_, other.array_, sizeof(array_));

// Swap with our vector. Leaves the old contents in |other|.

// We could destroy the old content instead, but this is equally

// valid.
vector_.swap(other.vector_);
return \*this;
}
private:
int\* pointer_;
char array_\[42\];
std::vector&lt;float&gt; vector_;
};

<http://pastebin.com/raw.php?i=WMZg2y3C>

### 10. Don’t go cray cray with move semantics.

Move-constructing is always as cheap or cheaper than copy-constructing. So you
could use it in every situation possible. But it may be adding very little value
(or making code misleading).

Almost always, just use a copy constructor. For example gfx::Point is 2
integers, a move constructor would do nothing different than a copy constructor.
There’s no reason to write a move constructor for this class, or to use
std::move() when using Points.

class Point {

int x_;

int y_;

...

Point(const Point& p) { x_ = p.x_; y_ = p.y_; }

Point(Point&& p) { x_ = p.x_; y_ = p.y_; }

};

Move constructors are especially valuable for types that hold a pointer to
something large, since moving a pointer is much cheaper than copying everything
it points to. Other scenarios benefit less or not at all.

### 11. Don’t return references. Don’t use std::move() on rvalues.

Functions return an rvalue without you doing anything, they already do this (see
point #1). So you don’t need to do anything to make this happen. You should not
see functions that return a MyType&&. Simply return MyType.

MyType&& MakeMyType() { // This is WRONG. The return type should not be

// a reference.

MyType type;

return std::move(type); // This is usually WRONG. Returning will

// already make it an rvalue.
}

Just as returning a MyType& would be wrong (returning a reference to a temporary
object), returning a MyType&& is wrong for the same reasons.

Putting std::move() on the return call actually makes things less efficient, it
interferes with the compiler doing RVO (Return Value Optimization). You can read
a previous chromium-dev thread about this in the context of scoped_ptr:
[link](https://groups.google.com/a/chromium.org/d/msg/chromium-dev/ICA3hiR4Z3A/Bi_ec46cAQAJ).

However, if the types of the variable and the return type do not match exactly,
then you will have to use std::move() in order to convert without creating an
extra temporary.

std::unique_ptr&lt;MyType&gt; MakeMyType()

{

std::unique_ptr&lt;ChildClassOfMyType&gt; ptr;

// This call to std::move() is needed to convert from a pointer-to-the

// child class to a pointer-to-the parent.

return std::move(ptr);
}

Similarly do not use move() on the return value from calling a function. You
already have an rvalue, so casting it there adds no value, only noise.

// This is WRONG. It is casting an rvalue to an rvalue, which is not

// needed.

ProcessMyType(std::move(MakeMyType()));

### 12. std::move() is the same thing as our familiar Pass() function.

For some time, scoped_ptr::Pass() has been implemented as being the same thing
as std::move().

Places that previously called a.Pass() can be changed to call std::move(a)
instead, and they will be changed in the near future. It may help you to
consider this, if you are already familiar with Pass().

Using std::unique_ptr instead of scoped_ptr will require the use of std::move()
instead, as Pass() does not exist there (and is not needed). So code will be
change from:

scoped_ptr&lt;MyType&gt; t = ...;

CallFunction(t.Pass());

To instead be:

std::unique_ptr&lt;MyType&gt; t = ...;

CallFunction(std::move(t));

## Perfect forwarding

The [google style
guide](https://google.github.io/styleguide/cppguide.html#Rvalue_references)
allows the following: Use rvalue references only to define move constructors and
move assignment operators, or for perfect forwarding. We’ll talk about the
latter here.

So far, we’ve seen that we can write an rvalue reference in a move constructor,
or a move-assignment operator:

MyType(MyType&& type); // Move constructor

operator=(MyType&& type); // Move-assignment operator

In both of these cases, the type MyType is fixed, so we know that we have a
reference to something that is definitely an rvalue. However when writing a
templated method, we may want to support an unknown type instead, at which point
we don’t know if it is an rvalue.

template&lt;typename T&gt;
void ProcessAndStore(T&& var) { ... }

Here the argument type may have been one of two things:

- Passed by move (rvalue reference) if you had an rvalue: MyType&&

- Passed by reference (aka lvalue reference) otherwise: MyType&

(You can read more about [reference
collapsing](http://stackoverflow.com/questions/13725747/concise-explanation-of-reference-collapsing-rules-requested-1-a-a-2)
if you want more details about how these are the only two possibilities.)

Arguments in templates can be written this way to implement perfect forwarding.
This allows for arguments to be passed through the template to another method
without introducing any additional copies of the object (ie no copy or move
constructors occur inside the template):

- If we had written the template as taking a parameter T, then a new variable
would have to be constructed inside the templated method. This could be done
with a copy constructor, or a move constructor (using std::move() inside the
template method), but it would require some extra work (which is why we normally
don’t pass-by-value of course).

- If we had written it instead as taking a const T&, then no copy takes place
entering the method, which is great. But when passing the variable to another
method, if it takes the argument by value, a copy occurs.

- Lastly, if we had written T&, then an rvalue would not bind to it, causing a
compiler error.

The most generally correct way to implement this is with
[std::forward()](http://en.cppreference.com/w/cpp/utility/forward), which will
always do the right thing for you.

template&lt;typename T&gt;
void ProcessAndStore(T&& var) {
// Process
// ...
// The type of |var| could be an rvalue reference, which means we

// should pass an rvalue to Store. However, it could also be an lvalue

// reference, which means we should pass an lvalue.
// Note that just doing Store(var); will always pass an lvalue and

// doing Store(std::move(var)) will always pass an rvalue. Forward does

// the right thing by casting to rvalue only if var is an rvalue

// reference.
Store(std::forward&lt;T&gt;(var));
}

<http://pastebin.com/raw.php?i=rVRebELt>

### std::forward() is a conditional cast to an rvalue.

This sounds lot like std::move(), which also casts to an rvalue. But
std::forward() will avoid casting to an rvalue if the argument is an lvalue
reference. It’s implementation is conceptually something like:

if (is_lvalue_reference&lt;T&gt;::value)

return t;

return std::move(t);

That is, it preserves lvalue references. Why?

Because doing std::move() on an lvalue reference is bad! Code producing an
lvalue reference expects the object to remain valid. But code receiving an
rvalue reference expects to be able to steal from it. This leaves you with a
reference pointing to a potentially-invalid object.

// Although Chromium disallows non-const lvalue references, this

// situation is still possible in templates.
void Oops(MyType& type) {
// Move is destructive. It can change |type| which is an lvalue

// reference.
MyType local_type = std::move(type);
// Work with local_type.
// ...
}
int main(int argc, char \*\*argv) {
MyType type;
// Don't move type, just pass it.
Oops(type);
// Oops, this is a null pointer dereference.
\*type.pointer = 314;
}

<http://pastebin.com/raw.php?i=cPrYgGhr>

For perfect forwarding in templates (ie arguments of type T&&), use
std::forward() instead of std::move().