【问题标题】:Pass-by-value, overloading or perfect forwarding for class type parameters [closed]类类型参数的按值传递、重载或完美转发
【发布时间】:2017-01-17 14:53:37
【问题描述】:

每当我想到我的类的设计时,我都会问自己这些问题,我应该使用按值传递,我应该重载 const 左值引用和右值引用还是应该使用完美转发。

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

你是做什么的?

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

谢谢。

【问题讨论】:

  • 要求调查意见或选择的问题是堆栈溢出的糟糕问题。

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


【解决方案1】:

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

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

输出参数

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

输入/输出参数

对于修改已构造值的函数,请使用可变左值引用,即T&。这将阻止调用者传递一个临时的,但这实际上是一件好事:修改你将要丢弃的东西有什么意义?并不是说某些样式指南(尤其是 Google 的,还有 Qt 的)提倡在这种情况下使用原始指针 (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&amp;&amp; 是不合适的。

第二种情况是真正的右值引用,其中实参类型不是模板形参。在极少数情况下,可能适合在const arg&amp;arg&amp;&amp; 表单上重载,以避免不必要的移动。这应该只在您要在某处复制或移动参数的性能关键情况下是必需的(例如,std::vector 为其push_back() 方法执行此操作)——通常我会说最好采取按值传递参数,然后将其移动到位。

【讨论】:

    【解决方案2】:

    下面的内容仅适用于“默认”行为。像“正常”不是非常大的类型(“正常大小”的向量、字符串等),一开始似乎没有什么很昂贵的。

    简而言之: 随心所欲,但始终如一。 没有最佳实践可以保证您获得最佳性能。

    一些细节:

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

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

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

    以下是我个人的看法:

    我所做的是我更喜欢按价值的方法,如果我后来发现某些事情变得缓慢,我就会开始优化。我假设现代编译器足够聪明,可以避免不必要的复制,并且当他们看到它不再使用时,也可能只是移动对象。我尽量记住Return Value Optimization,以便编译器在必要时更容易在此处优化(仅返回一个对象或仅返回 r 值)。

    虽然我听说这种行为和优化潜力随着编译器的变化而变化。所以就像之前说的:使用你喜欢的/坚持一种方式,这样它就可以保持一致。

    【讨论】:

      【解决方案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."
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-17
        • 1970-01-01
        • 2015-04-22
        • 1970-01-01
        • 2015-11-05
        • 1970-01-01
        • 1970-01-01
        • 2011-05-24
        相关资源
        最近更新 更多