C++ 将 const std::string & 作为参数传递的日子结束了吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10231349/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 13:46:16  来源:igfitidea点击:

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

c++c++11

提问by Benj

I heard a recent talk by Herb Sutter who suggested that the reasons to pass std::vectorand std::stringby const &are largely gone. He suggested that writing a function such as the following is now preferable:

我听到赫伯·萨特 (Herb Sutter) 最近的一次演讲,他认为经过std::vectorstd::string经过的原因const &基本上已经消失了。他建议现在最好编写如下函数:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

I understand that the return_valwill be an rvalue at the point the function returns and can therefore be returned using move semantics, which are very cheap. However, invalis still much larger than the size of a reference (which is usually implemented as a pointer). This is because a std::stringhas 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.

我知道这return_val将是函数返回时的右值,因此可以使用非常便宜的移动语义返回。但是,inval仍然比引用(通常实现为指针)的大小大得多。这是因为 astd::string具有各种组件,包括指向堆的指针和char[]用于短字符串优化的成员。所以在我看来,通过引用传递仍然是一个好主意。

Can anyone explain why Herb might have said this?

谁能解释一下赫伯为什么会这么说?

采纳答案by Nicol Bolas

The reason Herb said what he said is because of cases like this.

赫伯之所以这么说,就是因为这样的案例。

Let's say I have function Awhich calls function B, which calls function C. And Apasses a string through Band into C. Adoes not know or care about C; all Aknows about is B. That is, Cis an implementation detail of B.

假设我有A一个调用 function 的函数B,它调用 function C。并A通过传递一个字符串B,并进入CA不知道或不关心C;都A知道是B。也就是说,C是 的实现细节B

Let's say that A is defined as follows:

假设 A 定义如下:

void A()
{
  B("value");
}

If B and C take the string by const&, then it looks something like this:

如果 B 和 C 使用字符串 by const&,那么它看起来像这样:

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

All well and good. You're just passing pointers around, no copying, no moving, everyone's happy. Ctakes a const&because it doesn't store the string. It simply uses it.

一切都很好。你只是在传递指针,没有复制,没有移动,每个人都很高兴。C需要 aconst&因为它不存储字符串。它只是使用它。

Now, I want to make one simple change: Cneeds to store the string somewhere.

现在,我想做一个简单的更改:C需要将字符串存储在某处。

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

Hello, copy constructor and potential memory allocation (ignore the Short String Optimization (SSO)). C++11's move semantics are supposed to make it possible to remove needless copy-constructing, right? And Apasses a temporary; there's no reason why Cshould have to copythe data. It should just abscond with what was given to it.

你好,复制构造函数和潜在的内存分配(忽略短字符串优化(SSO))。C++11 的移动语义应该可以删除不必要的复制构造,对吗?并A通过一个临时; 没有理由C必须复制数据。它应该带着被给予的东西潜逃。

Except it can't. Because it takes a const&.

除了它不能。因为它需要一个const&.

If I change Cto take its parameter by value, that just causes Bto do the copy into that parameter; I gain nothing.

如果我更改C为按值获取其参数,那只会导致B复制到该参数中;我一无所获。

So if I had just passed strby value through all of the functions, relying on std::moveto shuffle the data around, we wouldn't have this problem. If someone wants to hold on to it, they can. If they don't, oh well.

因此,如果我只是str通过所有函数按值传递,依靠std::move将数据打乱,我们就不会有这个问题。如果有人想坚持下去,他们可以。如果他们不这样做,哦,好吧。

Is it more expensive? Yes; moving into a value is more expensive than using references. Is it less expensive than the copy? Not for small strings with SSO. Is it worth doing?

是不是更贵了?是的; 移动到一个值比使用引用更昂贵。比副本便宜吗?不适用于具有 SSO 的小字符串。值得做吗?

It depends on your use case. How much do you hate memory allocations?

这取决于您的用例。你有多讨厌内存分配?

回答by justin

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

将 const std::string & 作为参数传递的日子结束了吗?

No. Many people take this advice (including Dave Abrahams) beyond the domain it applies to, and simplify it to apply to allstd::stringparameters -- Alwayspassing std::stringby 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.

没有。许多人将这个建议(包括 Dave Abrahams)采纳到它适用的领域之外,并简化它以适用于所有std::string参数——对于任何和所有任意参数和应用程序,始终std::string按值传递并不是“最佳实践”,因为这些优化谈话/文章关注适用于有限的案例集

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.

与以往一样,当您不需要 copy 时,通过 const 引用传递可以节省大量复制。

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?

然而 inval 仍然比引用的大小(通常作为指针实现)大很多。这是因为 std::string 具有各种组件,包括指向堆的指针和用于短字符串优化的成员 char[]。所以在我看来,通过引用传递仍然是一个好主意。谁能解释一下赫伯为什么会这么说?

If stack size is a concern (and assuming this is not inlined/optimized), return_val+ inval> return_val-- IOW, peak stack usage can be reducedby 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.

如果堆栈大小是一个问题(并假设这不是内联/优化的),return_val+ inval> return_val-- IOW,可以通过在此处传递值来减少峰值堆栈使用量(注意:ABI 的过度简化)。同时,通过 const 引用传递可以禁用优化。这里的主要原因不是为了避免堆栈增长,而是为了确保可以在适用的地方执行优化。

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.

通过 const 引用的日子还没有结束——规则只是比以前更复杂。如果性能很重要,您将明智地根据您在实现中使用的细节来考虑如何传递这些类型。

回答by B?ови?

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

这些函数在单独的编译单元中实现以避免内联。然后:
1. 如果将字面量传递给这两个函数,则不会看到性能有太大差异。在这两种情况下,都必须创建一个字符串对象
2. 如果您传递另一个 std::string 对象,foo2将胜过foo1,因为foo1会进行深复制。

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

在我的 PC 上,使用 g++ 4.6.1,我得到了以下结果:

  • 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
  • 引用变量:1000000000 次迭代 -> 时间流逝:2.25912 秒
  • 按值变量:1000000000 次迭代 -> 时间:27.2259 秒
  • 文字参考:100000000 次迭代 -> 时间:9.10319 秒
  • 文字按值:100000000 次迭代 -> 时间流逝:8.62659 秒

回答by CodeAngry

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.
  • 如果您不修改字符串(视为只读),请将其作为const ref&.
    const ref&显然需要在使用它的函数执行时保持在范围内)
  • 如果您打算修改它,或者您知道它将超出范围(线程),请将其作为 a 传递value,不要复制const ref&您的函数体内部。

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

cpp-next.com上有个帖子叫“要速度,靠价值!” . TL; 博士:

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.

你只需要知道你打算如何在函数体中使用参数。只读或非……以及它是否在范围内。

回答by bames53

Unless you actually need a copy it's still reasonable to take const &. For example:

除非你真的需要一份副本,否则拿const &. 例如:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

If you change this to take the string by value then you'll end up moving or copying the parameter, and there's no need for that. Not only is copy/move likely more expensive, but it also introduces a new potential failure; the copy/move could throw an exception (e.g., allocation during copy could fail) whereas taking a reference to an existing value can't.

如果您将其更改为按值获取字符串,那么您最终将移动或复制参数,而无需这样做。复制/移动不仅可能更昂贵,而且还会引入新的潜在故障;复制/移动可能会引发异常(例如,复制期间的分配可能会失败),而引用现有值则不会。

If you doneed a copy then passing and returning by value is usually (always?) the best option. In fact I generally wouldn't worry about it in C++03 unless you find that extra copies actually causes a performance problem. Copy elision seems pretty reliable on modern compilers. I think people's skepticism and insistence that you have to check your table of compiler support for RVO is mostly obsolete nowadays.

如果您确实需要一个副本,那么按值传递和返回通常是(总是?)最好的选择。事实上,我通常不会在 C++03 中担心它,除非你发现额外的副本实际上会导致性能问题。复制省略在现代编译器上似乎非常可靠。我认为人们对您必须检查 RVO 的编译器支持表的怀疑和坚持现在已经过时了。



In short, C++11 doesn't really change anything in this regard except for people that didn't trust copy elision.

简而言之,除了不信任复制省略的人之外,C++11 在这方面并没有真正改变任何东西。

回答by Yakk - Adam Nevraumont

Almost.

几乎。

In C++17, we have basic_string_view<?>, which brings us down to basically one narrow use case for std::string const&parameters.

在 C++17 中,我们有basic_string_view<?>,这使我们基本上归结为std::string const&参数的一个狭窄用例。

The existence of move semantics has eliminated one use case for std::string const&-- if you are planning on storing the parameter, taking a std::stringby value is more optimal, as you can moveout of the parameter.

移动语义的存在消除了一个用例std::string const&——如果您计划存储参数,则采用std::stringby 值更为优化,因为您可以move从参数中取出。

If someone called your function with a raw C "string"this means only one std::stringbuffer is ever allocated, as opposed to two in the std::string const&case.

如果有人使用原始 C 调用您的函数,"string"这意味着只std::string分配了一个缓冲区,而不是在这种std::string const&情况下分配了两个。

However, if you don't intend to make a copy, taking by std::string const&is still useful in C++14.

但是,如果您不打算复制,std::string const&在 C++14 中使用by仍然很有用。

With std::string_view, so long as you aren't passing said string to an API that expects C-style '\0'-terminated character buffers, you can more efficiently get std::stringlike functionality without risking any allocation. A raw C string can even be turned into a std::string_viewwithout any allocation or character copying.

使用std::string_view,只要您不将所述字符串传递给需要 C 风格'\0'终止字符缓冲区的 API ,您就可以更有效地获得std::string类似的功能,而不会冒任何分配的风险。原始 C 字符串甚至可以在std::string_view没有任何分配或字符复制的情况下转换为。

At that point, the use for std::string const&is when you aren't copying the data wholesale, and are going to pass it on to a C-style API that expects a null terminated buffer, and you need the higher level string functions that std::stringprovides. In practice, this is a rare set of requirements.

那时,for 的用途std::string const&是当您不批量复制数据,而是将其传递给需要空终止缓冲区的 C 样式 API 时,并且您需要提供的更高级别的字符串函数std::string。实际上,这是一组罕见的要求。

回答by Puppy

std::stringis 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.

std::string不是Plain Old Data(POD),它的原始大小并不是最相关的东西。例如,如果您传入一个超过 SSO 长度并在堆上分配的字符串,我希望复制构造函数不会复制 SSO 存储。

The reason this is recommended is because invalis 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 constreference could still be the better way to go.

推荐这样做的原因是因为它inval是从参数表达式构造的,因此总是适当地移动或复制 - 没有性能损失,假设您需要参数的所有权。如果你不这样做,const参考仍然是更好的方法。

回答by Howard Hinnant

I've copy/pasted the answer from this questionhere, and changed the names and spelling to fit this question.

我在这里复制/粘贴了这个问题的答案,并更改了名称和拼写以适应这个问题。

Here is code to measure what is being asked:

这是用于测量所询问内容的代码:

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

For me this outputs:

对我来说,这个输出:

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

The table below summarizes my results (using clang -std=c++11). The first number is the number of copy constructions and the second number is the number of move constructions:

下表总结了我的结果(使用 clang -std=c++11)。第一个数字是复制结构的数量,第二个数字是移动结构的数量:

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

The pass-by-value solution requires only one overload but costs an extra move construction when passing lvalues and xvalues. This may or may not be acceptable for any given situation. Both solutions have advantages and disadvantages.

按值传递解决方案只需要一次重载,但在传递左值和 x 值时需要额外的移动构造。对于任何给定的情况,这可能是也可能不可接受的。这两种解决方案各有优缺点。

回答by circlepi314

Herb Sutter is still on record, along with Bjarne Stroustroup, in recommending const std::string&as a parameter type; see https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in.

Herb Sutter 和 Bjarne Stroustroup 仍然被记录在案,推荐const std::string&作为参数类型;见https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in

There is a pitfall not mentioned in any of the other answers here: if you pass a string literal to a const std::string&parameter, it will pass a reference to a temporary string, created on-the-fly to hold the characters of the literal. If you then save that reference, it will be invalid once the temporary string is deallocated. To be safe, you must save a copy, not the reference. The problem stems from the fact that string literals are const char[N]types, requiring promotion to std::string.

这里的任何其他答案中都没有提到一个陷阱:如果将字符串文字传递给const std::string&参数,它将传递对临时字符串的引用,该字符串是即时创建的以保存文字的字符。如果您随后保存该引用,一旦临时字符串被释放,它将无效。为安全起见,您必须保存副本,而不是参考。问题源于这样一个事实,即字符串文字是const char[N]类型,需要提升到std::string.

The code below illustrates the pitfall and the workaround, along with a minor efficiency option -- overloading with a const char*method, as described at Is there a way to pass a string literal as reference in C++.

下面的代码说明了陷阱和解决方法,以及一个次要的效率选项——使用const char*方法重载,如Is there a way to pass a string literal as reference in C++ 中所述

(Note: Sutter & Stroustroup advise that if you keep a copy of the string, also provide an overloaded function with a && parameter and std::move() it.)

(注意:Sutter 和 Stroustroup 建议,如果您保留字符串的副本,还要提供带有 && 参数和 std::move() 的重载函数。)

#include <string>
#include <iostream>
class WidgetBadRef {
public:
    WidgetBadRef(const std::string& s) : myStrRef(s)  // copy the reference...
    {}

    const std::string& myStrRef;    // might be a reference to a temporary (oops!)
};

class WidgetSafeCopy {
public:
    WidgetSafeCopy(const std::string& s) : myStrCopy(s)
            // constructor for string references; copy the string
    {std::cout << "const std::string& constructor\n";}

    WidgetSafeCopy(const char* cs) : myStrCopy(cs)
            // constructor for string literals (and char arrays);
            // for minor efficiency only;
            // create the std::string directly from the chars
    {std::cout << "const char * constructor\n";}

    const std::string myStrCopy;    // save a copy, not a reference!
};

int main() {
    WidgetBadRef w1("First string");
    WidgetSafeCopy w2("Second string"); // uses the const char* constructor, no temp string
    WidgetSafeCopy w3(w2.myStrCopy);    // uses the String reference constructor
    std::cout << w1.myStrRef << "\n";   // garbage out
    std::cout << w2.myStrCopy << "\n";  // OK
    std::cout << w3.myStrCopy << "\n";  // OK
}

OUTPUT:

输出:

const char * constructor
const std::string& constructor

Second string
Second string
const char * constructor
const std::string& constructor

Second string
Second string

回答by digital_infinity

IMO using the C++ reference for std::stringis a quick and short local optimization, while using passing by value could be (or not) a better global optimization.

使用 C++ 参考的 IMOstd::string是一种快速而简短的局部优化,而使用按值传递可能是(或不是)更好的全局优化。

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::stringcopy constructor behavior.
  1. 如果你把所有的代码从外部写到内部函数,你就知道代码做了什么,你可以使用引用const std::string &
  2. 如果您编写库代码或在传递字符串的地方使用大量库代码,您可能会通过信任std::string复制构造函数的行为在全局意义上获得更多收益。