为啥要在 C++ 中按值传递对象 [重复]

Posted

技术标签:

【中文标题】为啥要在 C++ 中按值传递对象 [重复]【英文标题】:Why would you pass an object by value in C++ [duplicate]为什么要在 C++ 中按值传递对象 [重复] 【发布时间】:2012-08-07 23:15:10 【问题描述】:

可能重复: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++ 调用约定问题,询问(比如说)值和引用之间的差异 - 但我找不到一个问“为什么?”的问题。)

【问题讨论】:

每次您需要副本时怎么办?添加Obj o2(o); 作为第一行?这似乎毫无意义。另外,移动语义。 @delnan - 谢谢。我通常按​​照您的描述进行操作,因为我认为这是函数的实现细节,不应构成其外部签名的一部分。 @Nemo。对了谢谢。投票关闭!可悲的是,蜂巢思维是一种比 SO 内置的更好的搜索工具...... @Roddy:不用担心。不幸的是,在 SO 上找到旧答案肯定变得越来越难。 @Roddy:你的意思是引用然后在第一行的函数中复制? 【参考方案1】:

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

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

【讨论】:

【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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

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

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

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

如果函数需要进行复制,那么如果复制的源是临时的,那么在界面中这样做允许编译器执行复制省略,从而提高效率。

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

如果函数需要制作副本,即使副本不能被删除,并且对于支持它的类型,内容将被移动(常用术语object 将被移动,但只有内容被移动),这再次将比内部复制更有效。

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

【讨论】:

【参考方案4】:

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

【讨论】:

谢谢。 “通过价值传递对转让所有权很有用”你能详细说明一下吗?【参考方案5】:

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

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.

【讨论】:

【参考方案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;

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

【讨论】:

“想要速度?传递价值”文章 +1

以上是关于为啥要在 C++ 中按值传递对象 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

是否传递指针参数,在 C++ 中按值传递?

为啥我要在 C 中按值传递函数参数?

为啥不允许将数组按值传递给 C 和 C++ 中的函数?

为啥要在 C 中声明一个只包含数组的结构?

C++:为啥对于内置(即类 C)类型,按值传递通常比按引用传递更有效

我应该如何在 jna 中按值从 Java 传递和返回 unsigned int 到 C/C++