当用于初始化另一个对象时,为什么要按值传递参数

Why should arguments be passed by value when used to initialize another object?

本文关键字:为什么 按值传递 参数 用于 初始化 一个对象      更新时间:2023-10-16

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

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

传统的方法是将strings参数声明为std::vector<std::string> const&并复制该参数。上面构造函数的值参数也需要复制!

为什么按值传递比按const&传递更可取?

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

当参数通过值传递时,strings对象显然不在其他地方使用,并且其内容可以移动。移动构造扩展到复制对象可能仍然相对便宜。例如,在std::vector<std::string>的情况下,移动只是将几个指针复制到新对象,并设置几个指针来指示原始对象不应该释放任何内容。

不过,仍然有必要创建这个论点。但是,在不创建新对象的情况下,可能会取消参数的创建。例如

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

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

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

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

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

也就是说,通过传递值可以完全消除副本,只进行移动。在最坏的情况下,当无论如何都需要复制参数时,需要执行额外的移动。由于移动通常被认为是一种廉价的操作,因此需要转移的对象的总体传递值会带来更好的性能。

语句

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

是的,从C++11开始,这要归功于移动语义的引入。它可以概括为:

当函数需要其某个参数的副本时,请按值传递。

这一点在"想要速度?通过价值传递"一文中有很好的阐述。

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