在C ++ 11中,pass-by-value是一个合理的默认值吗?

时间:2011-09-29 04:59:33

标签: c++ coding-style c++11

在传统的C ++中,将值传递给函数和方法对于大型对象来说很慢,并且通常不赞成。相反,C ++程序员倾向于传递引用,这更快,但它引入了围绕所有权的各种复杂问题,特别是围绕内存管理(如果对象是堆分配的)

现在,在C ++ 11中,我们有Rvalue引用和移动构造函数,这意味着可以实现一个大的对象(如std::vector),它可以很方便地通过值传入和传出函数

那么,这是否意味着默认值应该是为std::vectorstd::string等类型的实例传递值?自定义对象怎么样?什么是新的最佳实践?

4 个答案:

答案 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 AbrahamsXiang Fan上的这些博文。

答案 3 :(得分:0)

函数的签名应反映其预期用途。可读性对于优化器也很重要。

这是优化器创建最快的代码的最佳先决条件-至少从理论上讲,如果不是现实的话,则是几年后的现实。

在参数传递的上下文中,性能注意事项经常被高估。完美转发就是一个例子。像emplace_back这样的函数通常都很短并且内联。