Skip to content

Commit 4610136

Browse files
author
Break Yang
committed
Add a section about copy elision in C++.
1 parent 06be12e commit 4610136

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,32 @@ See [C++ Programmer's Toolbox](#c-programmers-toolbox) for details.
169169
you have to.
170170
3. **Trailing Return Type Syntax**
171171
* When in lambda. Period.
172-
172+
4. **Return Value of Complicated Type**
173+
174+
Returning values of simple types such as `int`, `int64_t`, `bool`
175+
may involves **copy**, but we hardly care. However it is rather
176+
important to **avoid copy** when returning values of complicated
177+
types because it can be rather expensive, e.g. `std::vector<int>`.
178+
179+
One of the common pattern is to pass in a `std::vector<int>`
180+
pointer as parameter and construct it in place as below:
181+
182+
```c++
183+
void DoWork(..., std::vector<int> *result) {
184+
...
185+
result->push_back(...);
186+
...
187+
result->push_back(...);
188+
...
189+
}
190+
```
191+
192+
I think this pattern should be discouraged in favor
193+
of
194+
[return value optimization](cases/return_value_optimization.md),
195+
because the latter is not only less error-prone, but also **much
196+
more readable**.
197+
173198
## Exceptions
174199

175200
1. **DO NOT** throw exceptions. Returns error code, and let the upper

cases/return_value_optimization.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Return Value Optimization and Copy Elision
2+
3+
## Problem
4+
5+
Suppose we wanted to write a function that returns a vector containing
6+
number from `0` to `n - 1`. The simplest version is
7+
**return-by-value**:
8+
9+
```c++
10+
std::vector<int> Range(int n) {
11+
std::vector<int> result;
12+
result.reserve(n);
13+
for (int i = 0; i < n; ++i) {
14+
result.push_back(i);
15+
}
16+
}
17+
```
18+
19+
And we can call it like:
20+
21+
```c++
22+
std::vector<int> x = Range(1000000);
23+
```
24+
25+
A valid question is: does this involves a copy or move of the huge
26+
``std::vector``?
27+
28+
## The Answer
29+
30+
If you are using a mordern compiler such
31+
as [gcc](https://gcc.gnu.org/) and [clang](http://clang.llvm.org/),
32+
the answer is **no** and **no**. A mechanism called **copy elision**
33+
will be enforced here as a **return value optimization**.
34+
35+
The standard has some detailed description on when **copy elision**
36+
happens, but in general copy elision happens when one (or both) of the
37+
conditions are met (There are other cases but are not important enough
38+
to be highlighted here):
39+
40+
1. Assigning a temporary to a variable of the same type.
41+
2. Returning a value right before it goes out of its scope.
42+
43+
When copy elision happens, the variable gets the value of the
44+
temporary without calling the copy constructor nor the move
45+
constructor. In fact it claims the temporary and becomes it.
46+
47+
## How about the Passing-A-Pointer Pattern
48+
49+
Another pattern that is widely used for this is pass-a-pointer:
50+
51+
```c++
52+
void Range(int n, std::vector<int> *result) {
53+
result->reserve(n);
54+
for (int i = 0; i < n; ++i) {
55+
result->push_back(i);
56+
}
57+
}
58+
```
59+
60+
This is acceptable, but I would say it is not as good bcause:
61+
62+
1. Do we assume the pointer `result` is initialized? A bad assumption
63+
can core dump here.
64+
2. It is far less readable, and it requires certain amount of mental
65+
work to realize that we are actually **returning** a vector.
66+
67+
## Conclusion
68+
69+
Stick to the copy elision approach and let the compiler lift the
70+
weight. Do not try to outsmart the compiler by making your code less
71+
readable.
72+
73+

0 commit comments

Comments
 (0)