按引用传递然后复制和按值传递在功能上有所不同吗?
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_ptr
。 In 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,这也意味着失去了一个优化机会
功能上有区别吗:
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_ptr
。 In 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,这也意味着失去了一个优化机会