在传统的C ++中,将值传递给函数和方法对于大型对象来说很慢,并且通常不赞成。相反,C ++程序员倾向于传递引用,这更快,但它引入了围绕所有权的各种复杂问题,特别是围绕内存管理(如果对象是堆分配的)
现在,在C ++ 11中,我们有Rvalue引用和移动构造函数,这意味着可以实现一个大的对象(如std::vector
),它可以很方便地通过值传入和传出函数
那么,这是否意味着默认值应该是为std::vector
和std::string
等类型的实例传递值?自定义对象怎么样?什么是新的最佳实践?
答案 0 :(得分:135)
这是一个合理的默认如果你需要在正文中复制。这就是Dave Abrahams is advocating:
指南:不要复制你的函数参数。相反,按值传递它们,让编译器进行复制。
在代码中,这意味着不要这样做:
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
传递仍然是合理的。
答案 1 :(得分:71)
在几乎所有情况下,您的语义都应该是:
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
所有其他签名应该只是谨慎使用,并且有充分的理由。编译器现在几乎总是以最有效的方式解决这些问题。您可以继续编写代码!
答案 2 :(得分:10)
如果在函数体内部需要对象的副本或者只需要移动对象,则按值传递参数。如果您只需要对对象进行非变异访问,请通过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和Xiang Fan上的这些博文。
答案 3 :(得分:0)
函数的签名应反映其预期用途。可读性对于优化器也很重要。
这是优化器创建最快的代码的最佳先决条件-至少从理论上讲,如果不是现实的话,则是几年后的现实。
在参数传递的上下文中,性能注意事项经常被高估。完美转发就是一个例子。像emplace_back
这样的函数通常都很短并且内联。