提问人:qwark 提问时间:9/9/2016 最后编辑:Barryqwark 更新时间:9/9/2016 访问量:1254
类类型参数的按值传递、重载或完美转发 [已关闭]
Pass-by-value, overloading or perfect forwarding for class type parameters [closed]
问:
每当我思考我的类设计时,我都会问自己这些问题,我应该使用传递值,我应该在常量左值引用和右值引用上重载,还是应该使用完美转发。
我经常使用按值传递,因为移动类型便宜,而且我几乎从不使用完美的转发。当只有 1 个参数时,我会重载,如果我真的需要 perf,可能会重载 2 个。
你是做什么工作的?
您是否有简单的经验法则来决定如何传递参数,用于成员/非成员函数,也用于构造函数和所有复制/赋值人员。
谢谢。
答:
接下来的内容仅适用于“默认”行为。就像“正常”不是真正的大类型(“正常大小”的向量、字符串等)一样,没有什么一开始就看起来很贵。
简而言之:做任何你喜欢的事情,但要保持一致。没有最佳实践可以保证您获得最佳性能。
一些细节:
我曾经参加过一个会议,有 3 个受欢迎的 C++ 人(Herb Sutter、Andrei Alexandrescu 和 Scott Meyers)讨论了这个问题,每个人都对最佳“默认”行为有不同的看法。
全部通过常量引用或完全转发或仅按值。
所以你不会在这里得到一个完美的答案。编译器也可以以不同的方式进行优化等。
以下是我个人对此的看法:
我所做的是我更喜欢按值方法,如果我后来注意到某些事情变得很慢,我就会开始优化。我认为现代编译器足够聪明,可以避免不必要的复制,并且当他们看到对象不再使用时,也可以移动对象。我试着记住返回值优化,以便编译器在必要时更容易在这里进行优化(要么只返回一个对象,要么只返回 r 值)。
虽然我听说过这种行为和优化潜力从编译器到编译器的变化。所以就像之前说的:使用你喜欢的东西/坚持一种方式,这样它就是一致的。
因此,以下所有内容都是基于意见的,但这些是我在考虑 API 时倾向于遵循的规则。与往常一样,在C++中,有很多方法可以完成同样的事情,人们会对什么是最好的有不同的看法。
我们需要考虑三种参数:输入参数、输出参数和输入/输出参数。 后两者很简单,所以我们先介绍它们。
输出参数
不要使用它们。认真地。如果您的函数要返回一个新对象,则按值返回它。如果要返回多个新对象,请按 (或 ) 中打包的值返回它们。调用方可以使用(或 C++ 17 中的结构化绑定)再次解压缩它们。这为调用方提供了最大的灵活性,并且使用 RVO 时,它的效率不亚于任何其他方法。std::tuple
std::pair
std::tie
输入/输出参数
对于修改已构造值的函数,请使用可变左值引用,即 .这将防止调用者传递临时消息,但这实际上是一件好事:修改您要丢弃的东西有什么意义?并不是说一些风格指南(特别是 Google 的,还有 Qt)主张在这种情况下使用原始指针 (),因此在调用站点上很明显会修改参数(因为你需要说),但我个人认为这并不令人信服。T&
T*
f(&arg)
在参数中
对于纯输入参数,函数不会修改传递给它的参数,事情就稍微复杂一些了。一般来说,最好的建议是传递 lvalue-reference-to-,即 .这将允许调用方同时传递左值和右值。但是,对于小对象 (),例如 ,改为按值传递可能更有效。const
const T&
sizeof(T) <= sizeof(void*)
int
不过,一个例外是,如果您要获取传递的参数的副本,例如在构造函数中;在这种情况下,最好按值获取参数,因为编译器可以将其转换为右值的移动。
T&&呢?
在两种情况下,使用 的参数是合适的。第一种是模板化转发函数,其中参数的类型是模板类型,即T&&
template <typename T>
decltype(auto) func(T&& arg) {
return other_func(std::forward<T>(arg));
}
在本例中,尽管参数看起来像是右值引用,但它实际上是一个转发引用(有时称为通用引用)。仅使用转发引用通过 ;如果你关心参数的值类别,那么就不合适了。std::forward
T&&
第二种情况是实数右值引用,其中参数类型不是模板参数。在极少数情况下,最好在 AND 窗体上重载,以避免不必要的移动。这应该只在性能关键的情况下是必要的,在这种情况下,你要复制或移动参数到某个地方(例如,对其方法执行此操作)——一般来说,我会说最好按值获取参数,然后将其移动到适当的位置。const arg&
arg&&
std::vector
push_back()
接口应表达意图。
当用户抱怨时,应该进行优化。
对我来说,以下接口有不同的含义:
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."
评论