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

Why would you pass an object by value in C++

本文关键字:值传 对象 C++ 为什么      更新时间:2023-10-16

可能的重复项:
C++按值传递还是按常量引用传递更好?

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

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

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

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

为什么C++实际上需要按值传递和按常量引用传递,并且 - 是否允许编译器在适当的情况下自动将调用转换为常量引用(和从常量引用传递)?

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

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

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

  • 因为Obj适合寄存器,所以按值传递和按值传递会更有效

仍然在 03 C++年,在过去几年中(荒谬,因为似乎有些文章在将近 10 年前就推荐了这一点,但没有真正的共识),

  • 如果函数需要创建副本,则在接口中执行此操作允许编译器在副本的源是临时时执行复制省略,因此可以更高效。

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

  • 如果函数需要创建副本,即使无法省略副本,并且对于支持它的类型,内容将被移动(在通俗的行话中,对象将被移动,但只有内容被移动),这又比内部复制更有效。

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

当然,

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 的 by-value 参数使用的位置。

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

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

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

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

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

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

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

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.

如果对象是可变的,则按值传递会为接收方提供自己的副本以供使用,并在合理更改的位置进行合理的更改,而不会影响调用方的副本 - 始终假设它是一个足够深的副本。

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

为什么C++实际上需要按值传递和按常量引用传递,并且 - 是否允许编译器在适当的情况下自动将调用转换为常量引用(和从中转换)?

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

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

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

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

但是,此副本永远不会被省略:

Obj a;
foo(a);

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