【问题标题】:Why should arguments be passed by value when used to initialize another object?用于初始化另一个对象时,为什么要按值传递参数?
【发布时间】:2015-11-24 10:20:06
【问题描述】:

将对象传递给函数时,可以选择通过值或const& 传递参数。特别是当对象的创建成本可能很高并且它在内部发生变异或用于初始化另一个对象时,建议按值传递对象。例如:

class Foo {
    std::vector<std::string> d_strings;
public:
    Foo(std::vector<std::string> strings): d_strings(std::move(strings)) {}
    // ...
};

常规方法是将strings 参数声明为std::vector&lt;std::string&gt; const&amp; 并复制该参数。上面构造函数的value参数也需要复制!

为什么最好按值传递而不是通过const&amp; 传递?

【问题讨论】:

  • 按值传递是“推荐”,这可能有点大胆。我想说这绝对是叶代码的好建议,因为它易于教授,避免了复杂性,并且用户不需要拼写像 &amp;&amp; 这样的尴尬标点符号。然而,在通用库代码中,似乎更合适的是有两个重载(const string&amp;string&amp;&amp;),以免强制复制/移动(可能很便宜)。
  • @KerrekSB:创建一个函数通常比创建两个函数更不容易出错。我确实同意高质量的实现可能希望创建两个重载,尽管const&amp; 版本实际上只是在实际需要完成复制时避免了移动。
  • 另一个重要的决定因素是你是否知道具体的参数类型,或者你是否有通用代码。在前一种情况下,您可以对额外移动的成本更有信心。对我来说,区别不在于 QoI,而在于代码的通用性和重用水平。我对我的用户了解得越少,我就越不想限制他们。
  • move 并不便宜:移动std::array&lt;T, 1024&gt; 无论如何都会复制,所以通过 const 引用传递它。

标签: c++ c++11


【解决方案1】:

当通过const&amp; 传递strings 参数时,有一个保证副本:除了复制它之外,没有其他方法可以获取参数的副本。问题变成了:按值传递时有什么不同?

当参数通过值传递时,strings 对象显然不会在其他任何地方使用,并且它的内容可以移动。移动构造扩展复制对象可能仍然相对便宜。例如,在std::vector&lt;std::string&gt; 的情况下,移动只是复制一些指向新对象的指针并设置一些指针以指示原始对象不应释放任何东西。

尽管如此,仍然需要创建参数。但是,可以省略参数的创建而不创建新对象。例如

Foo f(std::vector<std::string>{ "one", "two", "three" });

将创建一个包含三个字符串的向量,并且很可能省略了 Foo 构造的参数构造。在最坏的情况下,参数是通过移动临时向量来构造的,也避免了副本。

当然,在某些情况下仍需要创建副本。例如,在这种情况下

std::vector<std::string> v{ "one", "two", "three" };
Foo                      f(v);

参数是由副本创建的。然后将现成的副本移动到成员。在这种情况下,通过const&amp; 传递会更好,因为只需要复制构造,而不是复制构造(创建参数),然后执行移动构造(创建成员)。

也就是说,按值传递可能会完全删除副本而只是移动。在最坏的情况下,当无论如何都需要复制参数时,需要执行额外的移动。由于移动通常被认为是一种廉价的操作,因此预期需要传输的对象的整体按值传递会产生更好的性能。

【讨论】:

  • 你见过 Herbs 在 cppcon2014 上谈论这个吗?您对此有何看法?就个人而言,我会给出一个更受限制的一般建议:“仅使用按值传递,如果您必须复制构造(而不是分配)内部的对象(当然还有小型/内置类型)” .
【解决方案2】:

声明

参数在用于初始化另一个对象时应该按值传递

是真的,从 C++11 开始,感谢引入了移动语义。 可以概括为:

当函数需要其参数之一的副本时,按值传递。

这实际上在“Want Speed? Pass by Value.”文章中已经很详细了。

概述是,由于您的函数无论如何都需要参数的副本,因此最好在调用站点而不是在被调用函数内部处理此副本。这是因为,如果函数需要一个副本的对象是一个右值,它仅在调用站点是已知的,因此启用了移动优化:调用上下文很清楚该对象即将到期,因此可以将它移动到复制该功能所需的内容。现在,如果要在函数本身内部进行复制,则源对象(在调用上下文中)是 Rvalue 的概念不会被转发到副本的实际位置,从而失去了移动的机会。

【讨论】:

  • 如果我的类包含一个std::array&lt;int, 100000&gt; 类型的数据成员,该成员将使用该类型的现有对象的值进行初始化,该怎么办?那绝对是“需要它的一个论点的副本”。
猜你喜欢
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
  • 2012-08-07
  • 2020-12-05
相关资源
最近更新 更多