Suppose we wanted to write a function that returns a vector containing
number from 0
to n - 1
. The simplest version is
return-by-value:
std::vector<int> Range(int n) {
std::vector<int> result;
result.reserve(n);
for (int i = 0; i < n; ++i) {
result.push_back(i);
}
}
And we can call it like:
std::vector<int> x = Range(1000000);
A valid question is: does this involves a copy or move of the huge
std::vector
?
If you are using a mordern compiler such as gcc and clang, the answer is no and no. A mechanism called copy elision will be enforced here as a return value optimization.
The standard has some detailed description on when copy elision happens, but in general copy elision happens when one (or both) of the conditions are met (There are other cases but are not important enough to be highlighted here):
- Assigning a temporary to a variable of the same type.
- Returning a value right before it goes out of its scope.
When copy elision happens, the variable gets the value of the temporary without calling the copy constructor nor the move constructor. In fact it claims the temporary and becomes it.
Another pattern that is widely used for this is pass-a-pointer:
void Range(int n, std::vector<int> *result) {
result->reserve(n);
for (int i = 0; i < n; ++i) {
result->push_back(i);
}
}
This is acceptable, but I would say it is not as good bcause:
- Do we assume the pointer
result
is initialized? A bad assumption can core dump here. - It is far less readable, and it requires certain amount of mental work to realize that we are actually returning a vector.
Stick to the copy elision approach and let the compiler lift the weight. Do not try to outsmart the compiler by making your code less readable.