按值传递是 C + + 11中合理的默认值吗?

在传统的 C + + 中,通过值传递到函数和方法对于大型对象来说是很慢的,并且通常不受欢迎。相反,C + + 程序员倾向于传递引用,这样更快,但是会引入各种各样关于所有权的复杂问题,特别是关于内存管理的问题(在对象是堆分配的情况下)

现在,在 C + + 11中,我们有了 Rvalue 引用和 move 构造函数,这意味着可以实现一个大型对象(比如 std::vector) ,通过值传入和传出函数非常便宜。

那么,这是否意味着默认情况下应该通过值传递类型(如 std::vectorstd::string)的实例?定制对象呢?新的最佳实践是什么?

27147 次浏览

这是一个合理的默认值,你需要在体内复制一份:

指导原则: 不要复制函数参数,而是通过值传递它们,然后让编译器进行复制。

在代码中,这意味着不要这样做:

void foo(T const& t)
{
auto copy = t;
// ...
}

但是这样做:

void foo(T t)
{
// ...
}

它的优点是调用者可以这样使用 foo:

T lval;
foo(lval); // copy from lvalue
foo(T {}); // (potential) move from prvalue
foo(std::move(lval)); // (potential) move from xvalue

您需要两个重载才能对引用(void foo(T const&);void foo(T&&);)进行相同的操作。

考虑到这一点,我现在将我的有价值的构造函数写成这样:

class T {
U u;
V v;
public:
T(U u, V v)
: u(std::move(u))
, v(std::move(v))
{}
};

否则,通过引用 const传递仍然是合理的。

在几乎所有情况下,您的语义应该是:

bar(foo f); // want to obtain a copy of f
bar(const foo& f); // want to read f
bar(foo& f); // want to modify f

所有其他签名都应该谨慎使用,并且要有充分的理由。编译器现在几乎总是以最有效的方式解决这些问题。您可以继续编写您的代码!

如果在函数体中需要对象的副本或只需要移动对象,则按值传递参数。如果只需要对对象的非变异访问,则通过 const&传递。

对象复制示例:

void copy_antipattern(T const& t) { // (Don't do this.)
auto copy = t;
t.some_mutating_function();
}


void copy_pattern(T t) { // (Do this instead.)
t.some_mutating_function();
}

物体移动的例子:

std::vector<T> v;


void move_antipattern(T const& t) {
v.push_back(t);
}


void move_pattern(T t) {
v.push_back(std::move(t));
}

非变异访问示例:

void read_pattern(T const& t) {
t.some_const_function();
}

有关理论基础,请参阅 Dave Abrahams向帆的这些博客文章。

函数的签名应该反映它的预期用途。可读性很重要,对于优化器也是如此。

这是优化器创建最快代码的最佳先决条件——至少在理论上是这样,如果在现实中不是这样,那么在几年后的现实中也是如此。

在参数传递的上下文中,性能方面的考虑往往被高估了。完美转发就是一个例子。无论如何,像 emplace_back这样的函数大多非常短并且是内联的。