【问题标题】:Is there any case where a return of a RValue Reference (&&) is useful?是否存在返回 RValue 引用 (&&) 有用的情况?
【发布时间】:2015-06-02 22:27:11
【问题描述】:

函数应该返回 RValue 引用是否有原因?一种技术、技巧、习语或模式?

MyClass&& func( ... );

我一般都知道returning references 的危险,但有时我们还是这样做了,不是吗? T& T::operator=(T) 只是一个惯用的例子。但是T&& func(...) 怎么样?是否有任何一般的地方可以让我们从中受益?与仅编写客户端代码相比,编写库或 API 代码时可能会有所不同?

【问题讨论】:

    标签: c++ c++11 move-semantics rvalue-reference


    【解决方案1】:

    合适的场合有少数,但比较少见。当您希望允许客户端从数据成员移动时,这种情况出现在一个示例中。例如:

    template <class Iter>
    class move_iterator
    {
    private:
        Iter i_;
    public:
        ...
        value_type&& operator*() const {return std::move(*i_);}
        ...
    };
    

    【讨论】:

    • 一个很好的例子。模式是,您希望 客户端代码移动 某些东西——允许他“窃取”。是的,当然。
    • 一般来说,一个移动的对象,当在 std::lib 中使用时,必须满足为它使用的 std::lib 的任何部分指定的所有要求。标准定义的类型必须另外保证它们从状态中移动是有效的。客户端可以使用该对象调用任何函数,只要该函数调用的值没有前置条件。
    • 最后,在上面的例子中,没有移动的对象。 std::move 不动。它只转换为右值。从该右值移动(或不移动)取决于客户端。该客户端只有在两次取消引用 move_iterator 时才会访问已移动的值,而不干预迭代器遍历。
    • 使用value_type而不是value_type&amp;&amp;作为返回类型不是更安全吗?
    • 是的,我想是的。但是在这种情况下,我认为额外的好处大于风险。 move_iterator 通常用于通用代码中,以将复制算法转换为移动算法(例如 vector::insert 的移动版本)。如果您随后提供一个带有昂贵副本的类型并移至通用代码,那么您将无偿添加一个额外的副本。例如,我正在考虑 array 。将其中的一堆移动插入向量时,您不想意外引入额外的副本。在风险方面,const X&amp; x = *i 非常罕见。我想我从来没有见过它。
    【解决方案2】:

    这是对 towi 评论的跟进。您永远不想返回对局部变量的引用。但你可能有这个:

    vector<N> operator+(const vector<N>& x1, const vector<N>& x2) { vector<N> x3 = x1; x3 += x2; return x3; }
    vector<N>&& operator+(const vector<N>& x1, vector<N>&& x2)    { x2 += x1; return std::move(x2); }
    vector<N>&& operator+(vector<N>&& x1, const vector<N>& x2)    { x1 += x2; return std::move(x1); }
    vector<N>&& operator+(vector<N>&& x1, vector<N>&& x2)         { x1 += x2; return std::move(x1); }
    

    这应该在所有情况下防止任何复制(和可能的分配),除非两个参数都是左值。

    【讨论】:

    • 虽然有可能,但通常不赞成这种做法,因为这种方法除了保存临时文件之外还有其自身的问题。见stackoverflow.com/questions/6006527
    • 那些返回调用不需要使用std::move()吗?
    • @wjl:问得好,但我不这么认为。 std::move 无需使用 std::move 即可工作。我认为 && 的演员在这里可以解决问题。
    • @Clinton 你的代码中没有强制转换,你必须return std::move(x2); 等等。或者你可以写一个右值引用类型的强制转换,但这正是move 所做的。
    • 代码只有在返回值未使用或分配给对象时才是正确的——但是您也可以按值返回并按值获取参数并让复制省略来完成事情。
    【解决方案3】:

    没有。只需返回值。一般来说,返回引用一点也不危险——它返回对 local 变量的引用,这是危险的。然而,在几乎所有情况下,返回一个右值引用都是毫无价值的(我猜你是在写 std::move 什么的)。

    【讨论】:

    • 我认为在 C++0x 的早期设计中曾有人建议像 move-assignT&amp;&amp; operator+(const T&amp;,T&amp;&amp;) 这样的东西应该返回一个 @ 987654323@。但是现在,在最终草案中,这已经消失了。这就是我问的原因。
    【解决方案4】:

    如果你确定被引用的对象在函数退出后不会超出范围,你可以通过引用返回,例如它是一个全局对象的引用,或返回类字段引用的成员函数等。

    这个返回引用规则对于左值和右值引用都是一样的。不同之处在于您希望如何使用返回的引用。正如我所看到的,通过右值引用返回是很少见的。如果你有功能:

    Type&& func();
    

    你不会喜欢这样的代码:

    Type&& ref_a = func();
    

    因为它有效地将 ref_a 定义为 Type&,因为命名的右值引用是一个左值,并且不会在此处执行实际移动。很像:

    const Type& ref_a = func();
    

    除了实际的 ref_a 是一个非常量左值引用。

    即使您直接将 func() 传递给另一个接受 Type&& 参数的函数,它也不是很有用,因为它仍然是该函数内部的命名引用。

    void anotherFunc(Type&& t) {
      // t is a named reference
    }
    anotherFunc(func());
    

    func() 和 anotherFunc() 的关系更像是一种“授权”,func() 同意 anotherFunc() 可能从 func() 获取返回对象的所有权(或者你可以说“窃取”)。但这个协议非常松散。调用者仍然可以“窃取”非常量左值引用。实际上,函数很少被定义为采用右值引用参数。最常见的情况是“anotherFunc”是一个类名,而 anotherFunc() 实际上是一个移动构造函数。

    【讨论】:

      【解决方案5】:

      另一种可能的情况:当您需要解压缩元组并将值传递给函数时。

      如果您不确定复制省略,这在这种情况下可能很有用。

      这样的例子:

      template<typename ... Args>
      class store_args{
          public:
              std::tuple<Args...> args;
      
              template<typename Functor, size_t ... Indices>
              decltype(auto) apply_helper(Functor &&f, std::integer_sequence<size_t, Indices...>&&){
                  return std::move(f(std::forward<Args>(std::get<Indices>(args))...));
              }
      
              template<typename Functor>
              auto apply(Functor &&f){
                  return apply_helper(std::move(f), std::make_index_sequence<sizeof...(Args)>{});
              }
      };
      

      除非您正在编写某种形式的 std::bindstd::thread 替换,否则这种情况非常罕见。

      【讨论】:

        猜你喜欢
        • 2011-08-11
        • 1970-01-01
        • 2012-08-16
        • 2017-10-31
        • 1970-01-01
        • 1970-01-01
        • 2019-10-12
        • 2021-01-28
        • 2014-12-03
        相关资源
        最近更新 更多