为什么要在C ++中按值传递对象?

时间:2012-08-10 15:59:25

标签: c++ calling-convention

  

可能重复:
  Is it better in C++ to pass by value or pass by constant reference?

我知道在C ++中传递值,指针和引用的区别,我会考虑在C ++中按值(而不是const引用)传递对象几乎总是编程错误。

void foo(Obj o); ... // Bad

void foo(const Obj &o); ... // Better

我能想到的唯一情况是,通过值而不是const引用传递的位置是对象小于引用的位置,因此传递值更有效。

但是,这肯定是编译器构建来确定的那种东西吗?

为什么C ++实际上需要通过值传递并通过const引用,并且 - 编译器是否允许自动将调用转换为(和来自)const引用(如果合适)?

(似乎有100个C ++调用约定问题,询问(说)值和引用之间的差异 - 但我找不到一个问“为什么?”。) < / p>

6 个答案:

答案 0 :(得分:10)

通过值传递的时间可能比使用const引用更好的问题对于不同版本的标准有不同的答案。

在好的旧C ++ 03和几年前,建议通过const引用传递任何不适合寄存器的东西。在这种情况下,答案是:

  • 因为Obj适合寄存器并且按值传递并且传递值会更有效

仍然在C ++ 03中,在过去的几年里(看起来很荒谬,因为看起来有些文章推荐这差不多10年了,但是没有真正的共识),

  • 如果函数需要进行复制,那么在界面中这样做允许编译器执行 copy-elision ,如果副本的源是临时的,所以它可以更有效率。

批准新的C ++ 11标准,并增加对 rvalue-references 的编译器支持,在许多情况下即使不能省略副本,也会再次

  • 如果函数需要进行复制,即使无法删除副本,对于支持它的类型,内容也将移动(用通用术语) 对象将被移动,但它只是被移动的内容),这将比内部复制更有效。

至于为什么两种不同的召集惯例,它们有不同的目标。按值传递允许函数修改参数的状态而不会干扰源对象。此外,源对象的状态也不会干扰该函数(考虑多线程环境,以及在函数仍在执行时修改源的线程)。

答案 1 :(得分:6)

当然,C ++具有pass-by-value的一个原因是因为它从C继承了它,并且删除它可能会破坏代码以获得很少的收益。

其次,正如您所注意到的,对于小于参考值的类型,传递值的效率会降低。

另一个不那么明显的情况是,如果你有一个函数需要它的参数的副本出于某种原因:

void foo(const Obj& obj)
{
    if(very_rare_check()) return;

    Obj obj_copy(obj);
    obj_copy.do_work();
}

在这种情况下请注意您正在强制复制。但是假设您使用另一个按值返回的函数的结果调用此函数:

Obj bar() { return Obj(parameters); }

如此称呼它:foo(bar());

现在,当您使用const引用版本时,编译器最终会生成两个对象:临时文件和foo中的副本。但是,如果按值传递,编译器可以将所有临时值优化到foo的by-value参数所使用的位置。

有关于此的精彩文章,并在http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

处移动语义

最后,实现某些运算符的规范方法是使用pass-by-value来避免运算符内的副本:

Obj operator+(Obj left, const Obj& right)
{
    return left += right;
}

注意这是如何让编译器在参数中生成副本,而不是在操作符的代码中强制复制或临时对象。

答案 2 :(得分:4)

如果我想在不影响原件的情况下对函数内的对象执行操作,我会按值传递:

A minus(A b){
    b.val=-b.val;
    return b;
}

答案 3 :(得分:3)

复制交换习惯用法使用passe by value来实现编译器生成的副本。

MyClass& operator=(MyClass value) // pass by value to generate copy
{
    value.swap(*this);            // Now do the swap part.
    return *this;
}

基本上在需要修改参数但不想触摸原件的情况下。在这些情况下,如果通过const引用,则需要手动在函数内创建副本。如果让编译器处理副本,此手动步骤将阻止编译器可以执行的某些优化。

MyClass a;
// Some code
a = MyClass(); // reset the value of a
               // compiler can easily elide this copy.

答案 4 :(得分:1)

如果对象是可变的,那么传递值会给接收者自己的副本以及合理的变化,而不会影响调用者的副本 - 总是假设它是一个足够深的副本。

这可以简化一些多线程情况下的思考。

答案 5 :(得分:1)

  

为什么C ++实际上需要传递值并通过const引用传递,并且 - 编译器是否允许自动将调用转换为(和)const引用(如果合适)?

让我先回答第二个问题:有时候。

允许编译器将副本删除到参数中,但仅限于传入rvalue临时值。例如:

void foo(Obj o);

foo((Obj()))); //Extra set of parenthesis are needed to prevent Most Vexing Parse

在编译方便的时候,可以省略将临时复制到参数参数中(即:不复制)。

但是,此副本永远将被忽略:

Obj a;
foo(a);

现在,第一个。 C ++需要两者,因为您可能希望将两者用于不同的事情。通过价值转移对转让所有权很有用;这在C ++ 11中更为重要,我们可以移动而不是复制对象。