通过引用传递然后复制和传递值在功能上有所不同吗?

wrh*_*all 15 c++ pass-by-reference pass-by-value

两者之间是否存在功能差异:

void foo(const Bar& bar) {
  Bar bar_copy(bar);
  // Do stuff with bar_copy
}
Run Code Online (Sandbox Code Playgroud)

void foo(Bar bar) {
  // Do stuff with bar
}
Run Code Online (Sandbox Code Playgroud)

Dre*_*ann 22

是的,有一个很有价值的区别.

void foo(Bar bar)可以复制构造移动构造bar,具体取决于调用上下文.

当传递临时值时foo(Bar bar),您的编译器可能能够直接构造临时位置bar.帽子提示模板男孩.

您的功能void foo(const Bar& bar)始终执行副本.

您的功能void foo(Bar bar) 可能会执行复制或移动,也可能两者都不执行.

  • 您还错过了一个优化机会,因为当参数的初始化程序是临时的时,复制或移动可以完全省略. (3认同)

gha*_*.st 13

是的,存在差异.虽然最明显的一个是函数的类型发生了变化(因而也就是函数指针的类型),但也有一些不太明显的含义:

可移动构造但不可复制构造 Bar

例如,假设以下调用foo:

foo(Bar());
Run Code Online (Sandbox Code Playgroud)

对于第一个版本,这将通过引用传递const bar,然后使用复制构造函数进行复制.对于第二个版本,编译器将首先尝试move-constructor.

这意味着,唯一的第二个版本可以由仅可移动构造的类型调用,例如std::unique_ptr.实际上,手动强制复制甚至不允许编译该功能.

显然,这可以通过添加一个轻微的复杂性来减轻:

void foo(Bar&& bar) {
    // Do something with bar.
    // As it is an rvalue-reference, you need not copy it.
}

void foo(Bar const& bar) {
    Bar bar_copy(bar);
    foo(std::move(bar_copy));
}
Run Code Online (Sandbox Code Playgroud)

访问说明符

有趣的是,还有另一个不同之处:检查访问权限的上下文.

请考虑以下事项Bar:

class Bar
{
    Bar(Bar const&) = default;
    Bar(Bar&&) = default;

public:
    Bar() = default;

    friend int main();
};
Run Code Online (Sandbox Code Playgroud)

现在,引用和复制版本将出错,而参数值为版本不会抱怨:

void fooA(const Bar& bar)
{
    //Bar bar_copy(bar); // error: 'constexpr Bar::Bar(const Bar&)' is private
}

void fooB(Bar bar) { } // OK
Run Code Online (Sandbox Code Playgroud)

由于我们已宣布main为朋友,因此允许以下呼叫(请注意,如果实际呼叫是在static成员函数中进行的话,则不需要该朋友Bar):

int main()
{
    fooB(Bar()); // OK: Main is friend
}
Run Code Online (Sandbox Code Playgroud)

Bar呼叫站点的完整性

正如在评论中所观察到的,如果您希望在呼叫站点Bar上成为不完整类型,则可以使用传递引用版本,因为这不需要呼叫站点能够分配对象类型.Bar

复制Elision副作用

C++ 11 12.8/31:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的方式来引用同一个对象[...]

  • [...]
  • 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作省略的复制/移动
  • [...]

显然,只有按值调用的版本符合此标准 - 在通过引用传递之后,参数毕竟被绑定到引用.除了可观察到的差异之外,这也意味着失去了优化机会.