c - 将const std::string作为一个参数传递过来?

  显示原文与译文双语对照的内容

我听说最近一次讲话中,herbsutter建议的原因通过 std::vectorstd::stringconst & 在很大程度上消失了。 他建议现在编写如下的函数:


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

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

任何人都能解释为什么 Herb?

时间:

Herb说的原因是因为这样的情况。

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

假设一个定义如下:


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

如果 const& 和C 通过获取字符串,那么它看起来像这样:


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

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

很好,你只是传递指针,没有复制,没有移动,高兴的人。 C 接受 const&,因为它不存储字符串。 它只是使用它。

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


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

你好,复制构造函数和潜在内存分配( 忽略 SSO ) 。 c++11语义的移动应该使得删除不必要的copy-constructing成为可能,对吧? 而且 A 通过了一个临时性的,没道理,为什么 C 应该得副本数据。 它应该只是为它提供的内容。

除非它不能,因为它需要一个 const&

如果我改变 C 以获取它的参数,那就会导致 B 做那个参数的复制;我没有得到任何东西。

所以,如果我通过所有函数通过值传递 str,依靠 std::move 来打乱数据,我们就不会有这个问题。 如果有人想留住它,他们可以。 如果他们没有,那么。

它是否更昂贵是,移动到一个值比使用引用更昂贵。 它比拷贝便宜? 对于带SSO的小字符串不适用。 是否值得做?

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

将const std::string作为一个参数传递过来?

它适用于不 。许多人认为这点也建议( 包括 Dave Abrahams') 以外的域,而且方便要将其应用于所有 std::string 参数 -- 总是 std::string 按值传递任意参数和应用程序,因为这些优化不是一个"最佳实践"对于任何和所有这些/文章将重点介绍应用只有一组限定的情况下谈判失败。

如果你正在返回一个值,改变参数或者获取值,那么通过值传递可以节省昂贵的复制并提供语法上的便利。

一如既往,路过常量引用保存复制当你不需要一个副本。

下面是具体的示例:

然而,不管引用的大小如何,它仍然比引用( 通常作为指针实现) 大很多。 这是因为 std::string 有各种组件,包括一个指向堆的指针和一个用于短字符串优化的成员字符 [] 。 所以在我看来通过引用仍然是一个好主意。 任何人都能解释为什么 Herb?

如果堆栈大小是一个关注( 假设这不是内联/优化的) return_val + inval> return_val --低,峰堆栈使用可以通过降低值(注意: oversimplification ) 。同时通过const引用传递,可以禁用优化。 这里的主要原因是避免堆栈增长,但是为了确保优化,可以在合适的地方执行 。

通过const引用传递的天数不超过 --,规则比以前复杂得多。 如果性能很重要,你将明智地考虑如何通过你在实现中使用的细节来传递这些类型。

这依赖于编译器的实现。

但是,它也取决于你使用的是什么。

让我们考虑下一个函数:


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

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

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

  • 引用变量:1000000000迭代-> 时间已过: 2.259秒 12秒
  • 变量值:1000000000迭代-> 时间: 27.225秒 9秒
  • 文本按引用:100000000迭代-> 时间已过: 9.103秒 19秒
  • 面值:100000000次迭代-> 时间已过: 8.626秒 59秒

除非你真的需要一个副本,否则使用 const & 仍然是合理的。 例如:


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

如果你更改这个值以获取字符串,那么你将最终移动或者复制参数,并且不需要。 不仅复制/移动可能更昂贵,而且还引入了一个新的潜在错误;复制/移动可能引发异常( e.g 。复制期间的分配可能失败),而对现有值的引用不能引发异常。

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


简而言之,C++11不会真正改变这方面的内容,除了那些不信任复制省略的人。

std::string 不是一个 POD,它的原始大小不是最相关的东西。 例如,如果你传递一个字符串的长度上面sso和分配在堆上,我希望不是复制的拷贝构造函数sso存储。

这是推荐的原因是 inval 构造的参数表达式,因此总是移动或复制为appropriate-没有性能损失,假设你需要论证的所有权。 如果没有,const 引用仍然是更好的方法。

我已经从复制/粘贴了这个问题的答案,并更改了名称和拼写来适应这个问题。

下面是度量被询问内容的代码:


#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:nn";
 string x;
 string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
 std::cout <<"ndo_something with xvalue:nn";
 string u = do_something(std::move(x));
#endif
 std::cout <<"ndo_something with prvalue:nn";
 string v = do_something(source());
}

对于我来说,输出:


$ 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&&)

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


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

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

简短回答: 长答案: !

  • ( 视为 read-only ) 如果你不会修改字符串,通过 const ref&
    ( const ref& 显然需要保持在作用域内,而使用它的函数执行)
  • 如果你打算修改它或你知道它能摆脱范围 ( 线程), 把它作为 value, 不要复制 const ref& 函数体内。

There was a post on cpp-next.com called "需要速度,通过值传递". ! TL ;DR:

磅的准则: 不要复制函数参数。 相反,通过值传递它们并让编译器进行复制。

翻译^

不复制你的函数参数 ---意味着: 如果你计划前修改参数值通过复制一个内部变量,论证而不是只使用一个值。

所以,不做这个 :


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

这里任务的:


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

当你需要修改函数体中的参数值时。

你只需要知道如何在函数体中使用参数。 Read-only或者不。。如果它在作用域内。

使用 C++的参考 std::string 是一个快速的局部优化,而通过按值传递可以是一个更好的全局优化。

答案是:它取决于情况:

  1. 如果你将所有代码从外部写入内部函数,你就知道代码的作用,你可以使用引用 const std::string &
  2. 如果你编写了库代码,或者使用了大量的库代码来传递字符串,那么通过信任 std::string 复制构造函数行为,你可能会获得更多的全局意义。

几乎几乎。

有ts basic_string_view<?>, 如果批准和并入C++17,基本上会把我们击倒一个狭窄的用例 std::string const& 参数。

存在语义取消了一个用例 std::string const& --存储参数,如果你正计划采取 std::string 值更优,可以 move 参数。

如果有人用原始的"string" 调用你的函数,这意味着只分配了一个 std::string 缓冲区,而不是 std::string const& 实例中的两个。

然而,如果你不打算复制,在 C++14 std::string const& 仍然是有用的。

使用 std::string_view,只要你没有将说的字符串传递给期望 C-style '' -terminated字符缓冲区的API,就可以更高效地获取 std::string,而不需要任何分配。 原始的C 字符串甚至可以不用任何分配或者字符复制就变成 std::string_view

此时,用于 std::string const& 当你不是批发复制数据,并将它传递下去C-styleapi,预计一个空终止缓冲区,和你需要 std::string 提供更高水平的字符串函数。 在实践中,这是一套罕见的需求。

问题是"const"是一个non-granular限定符。 "常数字符串引用"通常指的是"不修改这里字符串",而不是"不修改引用计数"。 只是没有办法, C++ 说 "const"成员。 它们都是,或者它们都不是。

为了 hack 在这个语言问题,stl允许"c( )"在你的例子反正 move-semantic副本,并尽职尽责地忽略"const"( 因此假定它不是 mem-mapped,因为它是或者 nano-thready,或者) 关于引用计数。 只要是 well-specified,这就很好了。

由于stl不,我有一个版本的字符串 const_casts <> 引用计数器,,你瞧你可以自由通过的cmstring常量引用和复制的深度函数,一整天,没有泄漏或问题。

由于 C++ 没有提供const粒度,编写一个良好的规范并制作一个闪亮的新"const可以移动字符串"( cmstring ) 对象是我看到的最好的解决方案。

...