这两者之间是否有功能上的区别:
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
}
和
void foo(Bar bar) {
// Do stuff with 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
。事实上,手动强制复制甚至不允许该函数编译。
显然,可以通过添加轻微的复杂性来缓解这种情况:
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
是友元函数,因此以下调用是允许的(请注意,如果例如实际调用发生在Bar
的静态成员函数中,则不需要友元函数):
int main()
{
fooB(Bar()); // OK: Main is friend
}
Bar
在调用点的完整性正如评论中所观察到的,如果您希望在调用点上Bar
成为不完整类型,可以使用按引用传递的版本,因为这不需要调用点能够分配Bar
类型的对象。
C++11 12.8/31:
当满足某些条件时,实现允许忽略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为仅是两种不同的引用方式,用来指代同一对象[...]。
- [...]
- 当未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv-非限定类型的类对象时,复制/移动操作可以通过直接构造临时对象进入省略的复制/移动的目标中而被省略
- [...]
显然,只有按值传递的版本符合此条件 - 在按引用传递后,参数绑定到一个引用上。除了观察结果有所不同之外,这也意味着失去了一种优化机会。
有一些不同之处。
void foo(const Bar& bar) {
Bar bar_copy(bar);
// Do stuff with bar_copy
}
即使 bar
是一个临时变量,也不能避免复制并且也不能避免移动 bar
。
bar
进行移动构造和从bar
中移动。 - Jarod42
Bar bar_copy(bar);
? - danielschemmel.cpp
文件中。 - LogicStuffBar
,因为它在函数调用前被放在堆栈上。调用者需要知道对象的大小,所以它必须被完全指定。引用和指针通常作为指针传递,具有已知的大小(即sizeof(void *)
),因此在进入函数之前不需要知道对象的大小。 - Mark Lakata