在任何情况下,返回RValue引用(&&)都是有用的吗?


78

函数应返回RValue引用吗?一种技术,技巧,成语或模式?

MyClass&& func( ... );

我知道一般会返回引用的危险,但是有时候我们还是这样做,不是吗?T& T::operator=(T)只是一个惯用的例子。但是T&& func(...)呢?有没有一般的地方可以从中受益呢?与仅编写客户端代码相比,编写库或API代码时可能有所不同吗?

Answers:


59

在少数情况下,这是适当的,但它们相对较少。在一个示例中,当您要允许客户端从数据成员转移时,就会出现这种情况。例如:

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

5
一个很好的例子。模式是,您希望客户端代码移动某些内容-允许他“窃取”。当然是。
towi 2011年

4
通常,在std :: lib中使用从对象移出对象时,必须满足为其使用的std :: lib的任何部分指定的所有要求。std定义的类型还必须保证从状态移出的类型有效。客户端可以使用该对象调用任何函数,只要该函数调用的值没有前提条件即可。
Howard Hinnant

3
最后,在上面的示例中,没有移动对象。std :: move不动。它仅转换为右值。由客户决定是否从该右值移开。如果该客户端两次取消引用move_iterator而不介入迭代器遍历,则该客户端仅会访问一个from-from值。
Howard Hinnant

3
value_type代替value_type&&返回类型使用会更安全吗?
fredoverflow 2011年

1
是的,我想是这样。但是,在这种情况下,我认为增加的收益大于风险。move_iterator通常在通用代码中用于将复制算法转换为移动算法(例如vector :: insert的move-version)。如果您随后提供一种带有昂贵副本的类型并移至通用代码,则会无意中添加了额外的副本。我在想array <int,N>例如。当将其中一些插入向量中时,您不想意外地引入额外的副本。在风险方面,这种const X& x = *i情况很少见。我认为我从未见过。
霍华德·辛南特

17

这是关于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); }

这应该在所有情况下都防止任何副本(以及可能的分配),除非两个参数均为左值。


1
尽管有可能,但人们通常对此不屑一顾,因为这种方法除了节省临时人员外,还有其他问题。参见stackoverflow.com/questions/6006527
sellibitze 2011年

2
那些回叫不需要使用std :: move()吗?
wjl 2011年

1
@wjl:好问题,但我不这么认为。std :: move无需使用std :: move就可以工作。我认为对&&的转换在这里起到了作用。
克林顿

1
@Clinton,您的代码中没有强制转换,您必须执行其他操作return std::move(x2);。或者您可以编写强制转换为rvalue引用类型的强制转换,但是move无论如何,这就是工作。
MM

1
该代码仅在返回值未使用或未分配给对象的情况下才是正确的-但是您也可能已经按值返回了并按值接受了参数,然后让copy Elision来完成它的工作。
MM

7

否。只需返回值即可。通常,返回引用完全没有危险-它返回对局部变量很危险的引用。但是,在几乎所有情况下(如果您正在写std::move东西),返回右值引用都是毫无价值的。


5
我认为C的早期设计阶段++ 0x中有,当有人提出东西,如一个时间布展分配T&& operator+(const T&,T&&)应返回&&。但是,最终草案现在已不复存在。这就是为什么我问。
towi 2011年

1

如果您确定所引用的对象在函数退出后不会超出范围,则可以按引用返回,例如,它是全局对象的引用,或者成员函数返回对类字段的引用等。

返回的参考规则与左值和右值参考相同。区别在于您要如何使用返回的引用。如我所见,通过右值引用返回的情况很少见。如果您具有功能:

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()实际上是一个移动构造函数。


0

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

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

这样的例子:

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替换,否则这种情况很少见。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.