I heard a recent talk by Herb Sutter who suggested that the reasons to pass std::vector
and std::string
by const &
are largely gone. He suggested that writing a function such as the following is now preferable:
std::string do_something ( std::string inval )
{
std::string return_val;
// ... do stuff ...
return return_val;
}
I understand that the return_val
will be an rvalue at the point the function returns and can therefore be returned using move semantics, which are very cheap. However, inval
is still much larger than the size of a reference (which is usually implemented as a pointer). This is because a std::string
has various components including a pointer into the heap and a member char[]
for short string optimization. So it seems to me that passing by reference is still a good idea.
Can anyone explain why Herb might have said this?
This highly depends on the compiler's implementation.
However, it also depends on what you use.
Lets consider next functions :
These functions are implemented in a separate compilation unit in order to avoid inlining. Then :
1. If you pass a literal to these two functions, you will not see much difference in performances. In both cases, a string object has to be created
2. If you pass another std::string object,
foo2
will outperformfoo1
, becausefoo1
will do a deep copy.On my PC, using g++ 4.6.1, I got these results :
IMO using the C++ reference for
std::string
is a quick and short local optimization, while using passing by value could be (or not) a better global optimization.So the answer is: it depends on circumstances:
const std::string &
.std::string
copy constructor behavior.Short answer: NO! Long answer:
const ref&
.(the
const ref&
obviously needs to stay within scope while the function that uses it executes)value
, don't copy theconst ref&
inside your function body.There was a post on cpp-next.com called "Want speed, pass by value!". The TL;DR:
TRANSLATION of ^
Don’t copy your function arguments --- means: if you plan to modify the argument value by copying it to an internal variable, just use a value argument instead.
So, don't do this:
do this:
When you need to modify the argument value in your function body.
You just need to be aware how you plan to use the argument in the function body. Read-only or NOT... and if it sticks within scope.
No. Many people take this advice (including Dave Abrahams) beyond the domain it applies to, and simplify it to apply to all
std::string
parameters -- Always passingstd::string
by value is not a "best practice" for any and all arbitrary parameters and applications because the optimizations these talks/articles focus on apply only to a restricted set of cases.If you're returning a value, mutating the parameter, or taking the value, then passing by value could save expensive copying and offer syntactical convenience.
As ever, passing by const reference saves much copying when you don't need a copy.
Now to the specific example:
If stack size is a concern (and assuming this is not inlined/optimized),
return_val
+inval
>return_val
-- IOW, peak stack usage can be reduced by passing by value here (note: oversimplification of ABIs). Meanwhile, passing by const reference can disable the optimizations. The primary reason here is not to avoid stack growth, but to ensure the optimization can be performed where it is applicable.The days of passing by const reference aren't over -- the rules just more complicated than they once were. If performance is important, you'll be wise to consider how you pass these types, based on the details you use in your implementations.
See “Herb Sutter "Back to the Basics! Essentials of Modern C++ Style”. Among other topics, he reviews the parameter passing advice that’s been given in the past, and new ideas that come in with C++11 and specifically looks at the idea of passing strings by value.
The benchmarks show that passing
std::string
s by value, in cases where the function will copy it in anyway, can be significantly slower!This is because you are forcing it to always make a full copy (and then move into place), while the
const&
version will update the old string which may reuse the already-allocated buffer.See his slide 27: For “set” functions, option 1 is the same as it always was. Option 2 adds an overload for rvalue reference, but this gives a combinatorial explosion if there are multiple parameters.
It is only for “sink” parameters where a string must be created (not have its existing value changed) that the pass-by-value trick is valid. That is, constructors in which the parameter directly initializes the member of the matching type.
If you want to see how deep you can go in worrying about this, watch Nicolai Josuttis’s presentation and good luck with that (“Perfect — Done!” n times after finding fault with the previous version. Ever been there?)
This is also summarized as ⧺F.15 in the Standard Guidelines.
std::string
is not Plain Old Data(POD), and its raw size is not the most relevant thing ever. For example, if you pass in a string which is above the length of SSO and allocated on the heap, I would expect the copy constructor to not copy the SSO storage.The reason this is recommended is because
inval
is constructed from the argument expression, and thus is always moved or copied as appropriate- there is no performance loss, assuming that you need ownership of the argument. If you don't, aconst
reference could still be the better way to go.