类型参数的按值传递,重载或完美转发

时间:2016-09-09 12:40:39

标签: c++ c++11 move-semantics pass-by-value perfect-forwarding

每当我想到我班级的设计时,我都会问自己这些问题,如果我使用pass by value,我应该重载const lvalue reference和rvalue reference还是应该使用完美转发。

我经常使用pass by value作为移动类型的便宜而且我几乎从不使用完美转发。只有1个参数时我会重载,如果我真的需要这个参数,那么可能是2。

你做什么?

您是否有简单的经验法则来决定如何传递参数,成员/非成员函数以及构造函数和所有复制/赋值人员。

感谢。

3 个答案:

答案 0 :(得分:1)

现在的内容仅适用于“默认”行为。就像“普通”不是很大的类型(“正常大小”的矢量,字符串等),一开始似乎都不贵。

简而言之: 做任何你喜欢的事情,但要保持一致。 没有最佳实践可以保证您获得最佳性能。

一些细节:

我曾经参加过有3位受欢迎的C ++人士(Herb Sutter,Andrei Alexandrescu和Scott Meyers)讨论这个问题的会议,每个人对最佳“默认”行为都有另一种看法。

全部通过const-reference或完美转发或仅按值。

所以你不会在这里得到一个完美的答案。编译器也可以进行不同的优化等。

以下是我个人对此的看法:

我所做的是我更喜欢按价值方法,如果我后来注意到某些事情变得缓慢,我就会开始优化。我假设现代编译器足够智能以避免不必要的副本,也可能只是在它们看到之后不再使用它时移动它。我试着记住Return Value Optimization,以便在必要时让编译器更容易优化(仅返回一个对象或仅返回r值)。

虽然我听说过这种行为和优化潜力从编译器转变为编译器。就像之前说过的那样:使用你喜欢的东西/坚持一种方式,这样就可以保持一致。

答案 1 :(得分:1)

所以以下所有内容都是基于意见的,但这些是我在考虑API时会遵循的规则。和C ++一样,有很多方法可以完成同样的事情,人们会对最好的东西有不同的看法。

我们需要考虑三种参数: in 参数, out 参数和 in / out 参数。后两者很简单,所以我们先介绍它们。

输出参数

不要使用它们。认真。如果您的函数将返回一个新对象,则按值返回它。如果您要返回多个新对象,请按std::tuple(或std::pair)打包的值返回。调用者可以使用std::tie(或C ++ 17中的结构化绑定)再次解压缩它们。这为调用者提供了最大的灵活性,使用RVO时,它的效率也不低于其他任何方法。

输入/输出参数

对于修改已构造值的函数,请使用可变左值引用,即T&。这会阻止调用者传递一个临时的,但这实际上是一件好事:修改你要扔掉的东西会有什么意义呢?并不是说某些样式指南(特别是Google,但也是Qt)主张在这种情况下使用原始指针(T*),因此在调用站点显然会修改参数(因为你需要说{ {1}}),但我个人认为这并不令人信服。

参数

对于纯输入参数,函数不会修改传递给它的参数,事情要复杂一些。一般来说,最好的建议是通过左值引用传递给f(&arg),即const。这将允许调用者传递左值和右值。但是,对于小对象(const T&),例如sizeof(T) <= sizeof(void*),可以更有效地传递值。

例外情况是,如果您要获取传递参数的副本,例如在构造函数中;在这种情况下,最好按值获取参数,因为编译器可以将其转换为rvalues的移动。

T&amp;&amp;?

怎么样?

在两种情况下,使用int形式的参数是合适的。第一个是模板转发函数,其中参数的类型是模板类型,即

T&&

在这种情况下,尽管参数看起来好像是右值引用,但它实际上是转发引用(有时称为通用引用)。仅使用转发引用通过template <typename T> decltype(auto) func(T&& arg) { return other_func(std::forward<T>(arg)); } 将内容传递给另一个函数;如果您关心参数的值类别,则std::forward不合适。

第二种情况是实际的右值引用,其中参数类型不是模板参数。在极少数情况下,在T&&const arg&表单上重载可能是合适的,以避免不必要的移动。这只应该在性能关键的情况下才有必要,在这种情况下你要在某个地方复制或移动参数(例如,arg&&为其std::vector方法执行此操作) - 通常我会说最好是按值进行参数,然后将其移动到位。

答案 2 :(得分:1)

接口应表达意图。

当用户抱怨时,应该进行优化。

对我来说,以下界面有不同的含义:

void foo(thing const& t); // "I won't modify your t. If it's copyable, I might copy it, but that's none of your concern."

void foo(thing t); // "Pass me a copy if you wish, or a temporary, or move your thing into me. What I do with t is up to me".

void foo(thing& t);  // "t will be modified."