15

可能重复:
在 C++ 中通过值传递还是通过常量引用传递更好?

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

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

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

我能想到的唯一可能适合按值传递而不是 const 引用的情况是对象小于引用的情况,因此按值传递更有效。

但是,这肯定是编译器旨在确定的那种事情吗?

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

(似乎有 100 多个 C++ 调用约定问题,询问(比如说)值和引用之间的区别——但我找不到一个问“为什么?”的问题。)

4

6 回答 6

10

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

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

  • 因为Obj适合一个寄存器并且传值和传值会更高效

仍然在 C++03 中,在过去的几年里(似乎有些文章在近 10 年前推荐了这个,但没有真正的共识),

  • 如果函数需要进行复制,那么如果复制的源是临时的,那么在接口中这样做允许编译器执行复制省略,因此它可以更有效。

随着新 C++11 标准的批准,以及增加编译器对rvalue-references的支持,在许多情况下,即使副本不能被删除,并且再次

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

至于为什么这两种不同的调用约定的问题,它们有不同的目标。按值传递允许函数在不干扰源对象的情况下修改参数的状态。此外,源对象的状态也不会干扰函数(考虑多线程环境,以及在函数仍在执行时修改源的线程)。

于 2012-08-10T16:09:20.587 回答
6

当然,C++ 具有按值传递的一个原因是因为它从 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.

http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/上有一篇关于这个和移动语义的很棒的文章

最后,实现某些运算符的规范方法是使用按值传递来避免运算符内部的副本:

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

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

于 2012-08-10T16:10:08.853 回答
4

如果我想对函数内的对象做一些事情而不影响原来的,我会按值传递:

A minus(A b){
    b.val=-b.val;
    return b;
}
于 2012-08-10T16:02:42.690 回答
3

复制交换习语使用按值传递来实现编译器生成的副本。

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.
于 2012-08-10T16:09:58.480 回答
1

如果对象是可变的,则通过值传递给接收者自己的副本以供使用和合理的更改,而不影响调用者的副本——总是假设它是一个足够深的副本。

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

于 2012-08-10T16:04:44.600 回答
1

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

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

允许编译器将副本省略到参数中,但前提是您临时传入右值。例如:

void foo(Obj o);

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

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

但是,这个副本永远不会被忽略:

Obj a;
foo(a);

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

于 2012-08-10T16:09:30.430 回答