Are the days of passing const std::string & as a p

2018-12-31 23:17发布

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?

标签: c++ c++11
13条回答
只靠听说
2楼-- · 2018-12-31 23:36

This highly depends on the compiler's implementation.

However, it also depends on what you use.

Lets consider next functions :

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

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 outperform foo1, because foo1 will do a deep copy.

On my PC, using g++ 4.6.1, I got these results :

  • variable by reference: 1000000000 iterations -> time elapsed: 2.25912 sec
  • variable by value: 1000000000 iterations -> time elapsed: 27.2259 sec
  • literal by reference: 100000000 iterations -> time elapsed: 9.10319 sec
  • literal by value: 100000000 iterations -> time elapsed: 8.62659 sec
查看更多
弹指情弦暗扣
3楼-- · 2018-12-31 23:41

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:

  1. If you write all the code from the outside to the inside functions, you know what the code does, you can use the reference const std::string &.
  2. If you write the library code or use heavily library code where strings are passed, you likely gain more in global sense by trusting std::string copy constructor behavior.
查看更多
只若初见
4楼-- · 2018-12-31 23:43

Short answer: NO! Long answer:

  • If you won't modify the string (treat is as read-only), pass it as const ref&.
    (the const ref& obviously needs to stay within scope while the function that uses it executes)
  • If you plan to modify it or you know it will get out of scope (threads), pass it as a value, don't copy the const ref& inside your function body.

There was a post on cpp-next.com called "Want speed, pass by value!". The TL;DR:

Guideline: Don’t copy your function arguments. Instead, pass them by value and let the compiler do the copying.

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:

std::string function(const std::string& aString){
    auto vString(aString);
    vString.clear();
    return vString;
}

do this:

std::string function(std::string aString){
    aString.clear();
    return aString;
}

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.

查看更多
与风俱净
5楼-- · 2018-12-31 23:44

Are the days of passing const std::string & as a parameter over?

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 passing std::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:

However inval is still quite a lot 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?

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.

查看更多
荒废的爱情
6楼-- · 2018-12-31 23:45

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.

slide 24

The benchmarks show that passing std::strings 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.

查看更多
无与为乐者.
7楼-- · 2018-12-31 23:49

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, a const reference could still be the better way to go.

查看更多
登录 后发表回答