In C++ computer programming, copy elision refers to a compiler optimization technique that eliminates unnecessary copying of objects.

Return value optimization (RVO) is a compiler optimization that involves eliminating the temporary object created to hold a function’s return value.

from Wikipedia

Start with an example

Considering the following code, how many times will the string copy constructor be called?

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
#include <iostream>
#include <vector>
#include <string>

using std::vector;
using std::string;
using std::cout;

struct OneStruct {
vector<string> v;
OneStruct(vector<string> in) {
v.swap(in);
}
};

string GenString() {
string result("foo");
return result;
}

vector<string> GenVector(int n) {
vector<string> result;
for (int i = 0; i < n; ++i) {
result.push_back(GenString());
}
return result;
}

int main() {
int N = 10;
OneStruct one_struct(GenVector(N));
}

You might think,

  • In GenString(), foo is returned N times, the constructor is called N times.
  • In GenVector(), the temporary returned value of GenString() is pushed to vector, that’s another N copy times.
  • If N is a large number, vector resizes automatically, it will copy values to new vector, roughly speaking, that’s O(N) times. Why? Ask in the comments if you have question.
  • In main(), the temporary returned vector is passed by value to the constructor of OneStruct, O(N) copy times.
  • In struct, it simply swap the value, O(1) time, no copy.

Looks reasonable, but in C++ 11 actual answer is ZERO.

As long as there are no two distinct names for the same value, it will not invoke the copy constructor. In the above example, the string can be named as v[0], in[0] or even without name at all (the return value of GenString()), but it never has more than one name at a time. The value is passed by two features: copy elision and move semantics.

Copy Elision

Copy elision happens whenever an object is initialized by copying another object of the same type, and the source object is no longer accessible afterwards, e.g. leave current scope. In this case, compiler treats it as two objects are holding the same place, just skip the copy constructor and replace the place with the new name. There are two major cases where copy elision would happen: returning a local variable inside a function, and initializing a variable with a temporary value.

Return a local variables

1
2
3
4
5
6
7
string dosth() {
string local_str;
// Some operations on local_str.
return local_str;
}

string out_str = dosth();

Some people prefer to create a function and return a pointer rather than a value, thinking that return a large object is expensive, because it invokes object copy. However, thanks to copy elision, the out_str will take the ownership of the local variable right after the function exits, it’s completely free.

Avoid object copy by passing by value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// It invokes string copy inside.
string doA(const string& a) {
string local_str = a;
// ...
}

// No string copy.
string doB(string b) {
string local_str = std::move(b);
// ...
}

doA(GenString());
doB(GenString());

It may come as a surprise if you are accustomed to always passing objects by const-reference for efficiency. In the second function, instead of taking a string by const reference, and then copying it in the function body, it takes the variable by value, and then cheaply move it to local variable. Why? In fact in the second function it moves the copy operation from the function body to outside caller, which happens to meet the condition of copy elision, so it saves the one-time copy operation.

Pay attention

Not all compilers support copy elision, check if it is enabled first.

The primary limitation of copy elision is that it applies only during initialization of the destination object (i.e. when it’s first created), and requires the source object to be completely inaccessible after the copy.

Generally speaking, you should prefer simpler, safer, more readable code, and only go for something more complex if you have concrete evidence that the complex version performs better and that the difference matters. That principle certainly applies to this technique: passing by const reference is simpler and safer, so it’s still a good default choice. However, if you’re working in an area that’s known to be performance-sensitive, or your benchmarks show that you’re spending too much time copying function parameters, passing by value can be a very useful tool to have in your toolbox.

reference