【发布时间】:2016-10-22 11:15:12
【问题描述】:
我什么时候应该将我的函数声明为:
void foo(Widget w);
相对
void foo(Widget&& w);?
假设这是唯一的重载(例如,我选择一个或另一个,而不是两者,也没有其他重载)。不涉及模板。假设函数foo 需要Widget 的所有权(例如const Widget& 不在此讨论范围内)。我对这些情况范围之外的任何答案都不感兴趣。 为什么这些限制是问题的一部分,请参阅帖子末尾的附录。
我和我的同事能想出的主要区别是右值引用参数迫使您明确说明副本。调用者负责制作显式副本,然后在您需要副本时将其与std::move 一起传递。在按值传递的情况下,复制的成本是隐藏的:
//If foo is a pass by value function, calling + making a copy:
Widget x{};
foo(x); //Implicit copy
//Not shown: continues to use x locally
//If foo is a pass by rvalue reference function, calling + making a copy:
Widget x{};
//foo(x); //This would be a compiler error
auto copy = x; //Explicit copy
foo(std::move(copy));
//Not shown: continues to use x locally
除此之外。除了在调用函数时强迫人们明确复制和更改你得到多少语法糖之外,这些还有什么不同?他们对界面有什么不同的看法?它们之间的效率更高还是更低?
我和我的同事已经想到的其他事情:
- 右值引用参数意味着您可以移动参数,但不强制要求它。您在调用站点传递的参数可能会在之后处于其原始状态。也有可能该函数会吃掉/更改参数而不调用移动构造函数,但假设因为它是一个右值引用,调用者放弃了控制。按值传递,如果你进入它,你必须假设发生了移动;别无选择。
- 假设没有省略,通过右值传递消除单个移动构造函数调用。
- 编译器有更好的机会通过值传递来省略复制/移动。任何人都可以证实这一说法吗?最好带有指向 gcc.godbolt.org 的链接,显示从 gcc/clang 生成的优化代码,而不是标准中的一行。我试图展示这一点可能无法成功隔离该行为:https://godbolt.org/g/4yomtt
附录: 为什么我要限制这个问题这么多?
- 没有重载 - 如果有其他重载,这将转为讨论按值传递与包含 const 引用和 rvalue 引用的重载集,此时重载集显然更有效并且更胜一筹。这是众所周知的,因此并不有趣。
- 没有模板 - 我对转发引用如何融入图片不感兴趣。如果您有转发引用,则无论如何都调用 std::forward 。转发参考的目标是在您收到东西时传递它们。副本不相关,因为您只需传递一个左值。众所周知,但并不有趣。
-
foo需要Widget的所有权(又名const Widget&) - 我们不是在谈论只读函数。如果该函数是只读的,或者不需要拥有或延长Widget的生命周期,那么答案就变成了const Widget&,这又是众所周知的,并不有趣。我还向您介绍了为什么我们不想谈论重载。
【问题讨论】:
-
为什么不直接使用
std::move而不是制作中间副本? -
@VermillionAzure - 如果我以后不打算使用该变量,我可以这样做。关键是如果我确实需要一个副本,它现在是明确的。该示例假设出于某种原因需要副本。
-
这取决于
foo对参数所做的事情。像这样的非成员函数需要获取参数的所有权是不寻常的。 -
这两个接口不可互换,因为传递值也采用左值。所以我不确定是否可以在不指定进一步限制使用的情况下进行有意义的比较。
-
这个问题相当广泛。函数是否会修改对象会产生很大的不同,如果您对其进行约束,以便函数始终修改对象以获得更多的主题答案。