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

Is passing by reference then copying and passing by value functionally different?

功能上有区别吗:

void foo(const Bar& bar) {
  Bar bar_copy(bar);
  // Do stuff with bar_copy
}

void foo(Bar bar) {
  // Do stuff with bar
}

有些不同。

void foo(const Bar& bar) {
  Bar bar_copy(bar);
  // Do stuff with bar_copy
}

不允许避免复制,即使 bar 是临时的,也不允许避免 bar 的移动。

是的,两者之间存在重要差异。

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

当一个临时文件被传递给 foo(Bar bar) 时,您的编译器可能能够在 bar 预期的位置直接构造 那个临时文件。向模板男孩致敬。

您的函数 void foo(const Bar& bar) 始终执行复制。

您的函数 void foo(Bar bar) 可以执行复制或移动,也可能两者都不执行。

是的,有区别。虽然最明显的一个是函数的类型发生了变化(因此函数指针的类型也发生了变化),但也有一些不太明显的影响:

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

例如,假设以下调用 foo

foo(Bar());

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

这意味着,只有第二个版本可以被只能移动构造的类型调用,比如 std::unique_ptrIn fact, the manually forced copy will not even allow compilation of the function.

显然,这可以通过添加一些复杂的东西来缓解:

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

访问说明符

有趣的是,还有一个区别:检查访问权限的上下文。

考虑以下 Bar

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

public:
    Bar() = default;

    friend int main();
};

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

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

void fooB(Bar bar) { } // OK

因为我们已经声明 main 为好友,下面的调用 is allowed(请注意,如果实际调用是在 static Bar的成员函数):

int main()
{
    fooB(Bar()); // OK: Main is friend
}

调用站点 Bar 的完整性

正如在评论中观察到的,如果您希望 Bar 在调用站点 成为不完整类型 ,则可以使用 pass-by -reference 版本,因为这不需要调用站点能够分配 Bar.

类型的对象

复制省略副作用

C++11 12.8/31:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object [...]

  • [...]
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • [...]

显然,只有传值调用版本符合这个条件——通过引用传递后,参数毕竟是绑定到一个引用上的。 Beyond an observable difference,这也意味着失去了一个优化机会