我最近听了Herb Sutter的一个演讲,他认为通过const &传递std::vector和std::string的原因已经基本消失了。他建议现在最好编写如下这样的函数:
std::string do_something ( std::string inval )
{
std::string return_val;
// ... do stuff ...
return return_val;
}
我知道return_val在函数返回时将是一个右值,因此可以使用move语义返回,这非常便宜。然而,inval仍然比引用(通常实现为指针)的大小大得多。这是因为std::string有各种组件,包括指向堆的指针和用于短字符串优化的成员char[]。所以在我看来,通过引用传递仍然是一个好主意。
谁能解释一下赫伯为什么会这么说?
简单的回答:不!长一点的回答:
如果不修改字符串(treat是只读的),则将其作为const ref&传递。(const ref&显然需要在使用它的函数执行时保持在作用域内)
如果你打算修改它,或者你知道它将超出范围(线程),将它作为一个值传递,不要在函数体中复制const ref&。
在cpp-next.com网站上有一篇文章叫做“想要速度,而不是价值!”TL;博士:
指南:不要复制函数参数。相反,应该按值传递它们,并让编译器执行复制。
^的翻译
不要复制你的函数实参——意思是:如果你打算通过将实参复制到内部变量来修改实参值,只需使用一个值实参即可。
所以,不要这样做:
std::string function(const std::string& aString){
auto vString(aString);
vString.clear();
return vString;
}
这样做:
std::string function(std::string aString){
aString.clear();
return aString;
}
当您需要修改函数体中的参数值时。
您只需要注意计划如何在函数体中使用参数。只读或非只读…如果它在范围内。
参见“Herb Sutter”回归基础!《现代c++风格要点》。在其他主题中,他回顾了过去给出的参数传递建议,以及c++ 11中引入的新思想,并特别介绍了按值传递字符串的思想。
基准测试显示,在函数无论如何都会复制std::strings的情况下,按值传递std::strings可能会显著变慢!
这是因为你强迫它总是做一个完整的拷贝(然后移动到适当的位置),而const&version将更新旧字符串,这可能会重用已经分配的缓冲区。
请看他的幻灯片27:对于“set”函数,选项1一如既往。选项2为右值引用添加了重载,但如果有多个参数,则会导致组合爆炸。
只有对于必须创建字符串(而不是更改其现有值)的“sink”参数,值传递技巧才有效。也就是说,形参直接初始化匹配类型的成员的构造函数。
如果你想知道你能有多担心这个问题,看看尼科莱·约苏蒂斯的演讲,祝你好运(“完美-完成!”n次后发现错误的前一个版本。你去过那里吗?)
这也总结为⧺F。标准指南中的15条。
更新
一般情况下,你需要声明"string"参数为std::string_view(按值)。这允许你像使用const std::string&一样有效地传递一个现有的std::string对象,也可以传递一个词汇字符串字面量(如“hello!”)而不复制它,并传递类型为string_view的对象,现在这些也在生态系统中是必要的。
例外情况是当函数需要一个实际的std::string实例,以便传递给另一个声明为接受const std::string&的函数。
简单的回答:不!长一点的回答:
如果不修改字符串(treat是只读的),则将其作为const ref&传递。(const ref&显然需要在使用它的函数执行时保持在作用域内)
如果你打算修改它,或者你知道它将超出范围(线程),将它作为一个值传递,不要在函数体中复制const ref&。
在cpp-next.com网站上有一篇文章叫做“想要速度,而不是价值!”TL;博士:
指南:不要复制函数参数。相反,应该按值传递它们,并让编译器执行复制。
^的翻译
不要复制你的函数实参——意思是:如果你打算通过将实参复制到内部变量来修改实参值,只需使用一个值实参即可。
所以,不要这样做:
std::string function(const std::string& aString){
auto vString(aString);
vString.clear();
return vString;
}
这样做:
std::string function(std::string aString){
aString.clear();
return aString;
}
当您需要修改函数体中的参数值时。
您只需要注意计划如何在函数体中使用参数。只读或非只读…如果它在范围内。
赫伯说那些话的原因就是因为这样的案子。
假设我有一个函数A,它调用函数B,函数B调用函数C, A将一个字符串通过B传递给C, A不知道也不关心C;A只知道B,也就是说,C是B的一个实现细节。
假设A的定义如下:
void A()
{
B("value");
}
如果B和C通过const&获取字符串,那么它看起来像这样:
void B(const std::string &str)
{
C(str);
}
void C(const std::string &str)
{
//Do something with `str`. Does not store it.
}
一切都很好。你只是传递指针,没有复制,没有移动,每个人都很开心。C接受一个参数&,因为它不存储字符串。它只是简单地使用它。
现在,我想做一个简单的改变:C需要将字符串存储在某个地方。
void C(const std::string &str)
{
//Do something with `str`.
m_str = str;
}
你好,复制构造函数和潜在的内存分配(忽略短字符串优化(SSO))。c++ 11的move语义应该可以消除不必要的复制构造,对吧?A只是暂时的;C没有理由复制数据。它应该带着给它的东西潜逃。
但它不能。因为它需要一个常量。
如果我改变C的参数值,这只会导致B对参数进行复制;我什么也得不到。
所以如果我在所有函数中都按值传递str,依靠std::move来打乱数据,我们就不会有这个问题。如果有人想留住它,他们可以做到。如果没有,那好吧。
会更贵吗?是的,移动到值中比使用引用代价更大。它比复制品便宜吗?不适合使用SSO的小字符串。值得做吗?
这取决于您的用例。你有多讨厌内存分配?