类类型参数的按值传递、重载或完美转发 [已关闭]

Pass-by-value, overloading or perfect forwarding for class type parameters [closed]

提问人:qwark 提问时间:9/9/2016 最后编辑:Barryqwark 更新时间:9/9/2016 访问量:1254

问:


想改进这个问题吗?通过编辑这篇文章来更新问题,使其仅关注一个问题。

7年前关闭。

每当我思考我的类设计时,我都会问自己这些问题,我应该使用传递值,我应该在常量左值引用和右值引用上重载,还是应该使用完美转发。

我经常使用按值传递,因为移动类型便宜,而且我几乎从不使用完美的转发。当只有 1 个参数时,我会重载,如果我真的需要 perf,可能会重载 2 个。

你是做什么工作的?

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

谢谢。

C++ C++11 移动语义传递 按值完美 转发

评论

0赞 Yakk - Adam Nevraumont 9/9/2016
要求对意见或选择进行调查的问题是堆栈溢出的糟糕问题。

答:

1赞 Hayt 9/9/2016 #1

接下来的内容仅适用于“默认”行为。就像“正常”不是真正的大类型(“正常大小”的向量、字符串等)一样,没有什么一开始就看起来很贵。

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

一些细节:

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

全部通过常量引用或完全转发或仅按值。

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

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

我所做的是我更喜欢按值方法,如果我后来注意到某些事情变得很慢,我就会开始优化。我认为现代编译器足够聪明,可以避免不必要的复制,并且当他们看到对象不再使用时,也可以移动对象。我试着记住返回值优化,以便编译器在必要时更容易在这里进行优化(要么只返回一个对象,要么只返回 r 值)。

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

4赞 Tristan Brindle 9/9/2016 #2

因此,以下所有内容都是基于意见的,但这些是我在考虑 API 时倾向于遵循的规则。与往常一样,在C++中,有很多方法可以完成同样的事情,人们会对什么是最好的有不同的看法。

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

输出参数

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

输入/输出参数

对于修改已构造值的函数,请使用可变左值引用,即 .这将防止调用者传递临时消息,但这实际上是一件好事:修改您要丢弃的东西有什么意义?并不是说一些风格指南(特别是 Google 的,还有 Qt)主张在这种情况下使用原始指针 (),因此在调用站点上很明显会修改参数(因为你需要说),但我个人认为这并不令人信服。T&T*f(&arg)

在参数中

对于纯输入参数,函数不会修改传递给它的参数,事情就稍微复杂一些了。一般来说,最好的建议是传递 lvalue-reference-to-,即 .这将允许调用方同时传递左值和右值。但是,对于小对象 (),例如 ,改为按值传递可能更有效。constconst T&sizeof(T) <= sizeof(void*)int

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

T&&呢?

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

template <typename T>
decltype(auto) func(T&& arg) {
    return other_func(std::forward<T>(arg));
}

在本例中,尽管参数看起来像是右值引用,但它实际上是一个转发引用(有时称为通用引用)。仅使用转发引用通过 ;如果你关心参数的值类别,那么就不合适了。std::forwardT&&

第二种情况是实数右值引用,其中参数类型不是模板参数。在极少数情况下,最好在 AND 窗体上重载,以避免不必要的移动。这应该只在性能关键的情况下是必要的,在这种情况下,你要复制或移动参数到某个地方(例如,对其方法执行此操作)——一般来说,我会说最好按值获取参数,然后将其移动到适当的位置。const arg&arg&&std::vectorpush_back()

2赞 Richard Hodges 9/9/2016 #3

接口应表达意图。

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

对我来说,以下接口有不同的含义:

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."