Big objects are traditionally passed to methods by reference. Some weeks ago I talked to a colleague about that and decided to investigate a bit further. As a result of my research I must admit that, starting with C++11, things have indeed changed. Depending on what happens inside the body of the called method, passing by value may be actually more efficient.


If you'd like to pass, let's say a pretty long std::string, into a method or function, which parameter type would you choose? See the following call of the function f():
std::string foo{"verylong"};
f(foo);

In traditional C++ (C++98) there are three function signatures I can think of (T being a placeholder for e.g. std::string):

void f(T param);
void f(T& param);
void f(const T& param);

For the first one (pass by value), the argument of type T is copied into param. This copying may involve pretty expensive operations, hence we tend use this only for small objects. The second one uses pass by reference, allowing modification of the argument inside the function. In the third version, the argument is also passed by reference, but modification is prohibited.

Two use cases

Now, the debate is about the first and the third version of the function signature. What if the argument is a big object but we insist on passing it by value? We may distinguish two different cases here:

1) The parameter is just used in the function:

void f(T param) {
	std::cout << param;
}

2) The parameter is copied:

void f(T param) {
	T copiedParam{param};
	// ...
}

With 1), the copy is thrown away right away after accessing it a single time. We might prefer const T& here to avoid the copy entirely. But in example 2), the function needs to create a copy anyway. In traditional C++, this is also not a good way to go, because there are two copies created.

Modern C++

With C++11 and move semantics, this looks totally different. We can write the following:

void f(T param) {
	T movedParam{std::move(param)};
}

Now, depending on the type of the argument, there is either only one copy created or even no copies at all!

If we pass in an rvalue, the argument still needs to be copied:

std::string foo{"verylong"};
f(foo);

But in this use case, the copying needs to happen anyway. And because the parameter is moved (not copied) inside the function body, that's all.

No what if the argument is an rvalue? This is the really interesting scenario:

f("verylong");

This is where the pass by value comes in really handy. The argument is first moved into the parameter and then moved again inside the function body. No copy involved!

Clues for the caller

This issue is also a matter of semantics. The user of an API may recognize what is going to happen to her arguments immediately from the function signature. A by-const-reference parameter would tell: "I will just use your argument or save a reference for later use". In contrast, a by-value parameter means: "I will take a copy of your argument or move it if possible".

What do you think? Leave me a comment via twitter: @ronalterde.

Good Reads