为什么在C++中会通过值传递对象?

15

可能是重复问题:
在C++中,按值传递还是按常量引用传递更好?

我了解在C ++中通过值、指针和引用传递的差别,并且我认为在C ++中将对象按值传递(而不是按const引用)几乎总是一种编程错误。

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

void foo(const Obj &o); ... // Better
我能想到唯一适合使用值传递而不是const引用的情况,是当对象比引用更小,因此通过值传递更高效。
但是,编译器难道不是为了确定这种情况而构建的吗?
为什么C++实际上需要值传递和const引用传递 - 编译器是否允许在适当的情况下自动将调用转换为(和从)const引用呢?
(似乎有数百个关于C++调用约定的问题,询问(例如)值和引用之间的差异 - 但我找不到一个问“为什么?”的。)

2
每次需要复制时怎么办?将Obj o2(o);作为第一行添加吗?这似乎毫无意义。此外,移动语义。 - user395760
@delnan - 谢谢。我通常按照您所描述的方式去做,因为我认为那是函数实现的细节,不应该成为其外部签名的一部分。 - Roddy
@Nemo。是的,谢谢。投票关闭!可悲的是,群体智慧比SO内置的搜索工具更好... - Roddy
@Roddy:别担心。很不幸,确实越来越难在SO上找到旧的答案了。 - Nemo
@GManNickG - 没错。虽然我现在看到了一些不理想的原因,但在我目前使用的编译器上,优化问题主要是无关紧要的。 - Roddy
显示剩余2条评论
6个回答

10

在何时使用值传递比使用const引用更好的问题,随着标准版本的不同而得到不同的答案。

在古老的C++03标准以及几年前,推荐的做法是通过const引用传递任何无法放入寄存器的内容。这种情况下,答案是:

  • 因为Obj可以放入寄存器,所以通过值传递和通过const引用传递将会更加高效。

仍然在C++03标准中,在最近几年(尽管有些文章近十年前就推荐了这种方式,但并没有真正的共识):

  • 如果函数需要复制,则在接口中进行复制允许编译器执行拷贝省略如果复制的源是临时的,则可以更有效地执行。

随着新的C++11标准的批准和对rvalue-references的日益增加的编译器支持,在许多情况下,即使不能省略复制操作,对于支持它的类型

  • 如果函数需要复制,甚至不能省略复制,并且对于支持它的类型,其内容将被移动(在通俗的术语中,对象将被移动,但实际上只转移了其内容),这比内部复制更高效。

至于为什么存在两种不同的调用约定,它们有不同的目标。通过值传递允许函数修改参数的状态而不干扰源对象。此外,源对象的状态也不会对函数产生干扰(考虑多线程环境和在函数执行过程中修改源对象的线程)。


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;
}

请注意,这使得编译器在参数中生成副本,而不是强制在运算符代码内部生成副本或临时对象。

“Want Speed? Pass by Value” 文章 +1 - Prashant Kumar

4

如果我想在函数内部对对象进行操作,但不影响原始对象,那么我会按值传递:

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

3
复制交换惯用语使用按值传递来实现编译器生成的复制。
MyClass& operator=(MyClass value) // pass by value to generate copy
{
    value.swap(*this);            // Now do the swap part.
    return *this;
}

基本上,在需要修改参数但不想触碰原始数据的情况下,您可以传递const引用。在这些情况下,如果您传递const引用,则需要在函数内部手动创建副本。这些手动步骤会阻止编译器在处理副本时执行某些优化。

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

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中更为重要,因为我们可以移动而不是复制对象。


谢谢。"按值传递对于转移所有权非常有用",您能详细解释一下吗? - Roddy

1

如果对象是可变的,按值传递会使接收方拥有自己的副本来使用,并在合理的情况下进行更改,而不会影响调用者的副本 - 假设它始终是足够深的副本。

这可能简化某��多线程情况下的思考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接