什么是“*这个的右值参考”?

时间:2013-07-08 07:35:09

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

标准还为成员函数调用引用限定符时,“*的rvalue引用”的最典型用例是什么?

顺便说一下,关于这种语言特征here有一个非常好的解释。

3 个答案:

答案 0 :(得分:19)

调用时,每个成员函数都有一个*this引用的隐式对象参数。

所以(a)这些正常的函数重载:

void f(const T&);
void f(T&&);

当被称为f(x)时; (b)这些成员函数重载:

struct C
{
    void f() const &;
    void f() &&;
};

当像x.f()一样被调用时 - (a)和(b)调度具有相似的可行性和排名。

因此用例基本相同。它们是支持移动语义优化。在rvalue成员函数中,您基本上可以掠夺对象资源,因为您知道它是一个即将到期的对象(即将被删除):

int main()
{
    C c;
    c.f(); // lvalue, so calls lvalue-reference member f
    C().f(); // temporary is prvalue, so called rvalue-reference member f
    move(c).f(); // move changes c to xvalue, so again calls rvalue-reference member f
}

例如:

struct C
{
    C operator+(const C& that) const &
    {
        C c(*this); // take a copy of this
        c += that;
        return c;
    }

    C operator+(const C& that) &&
    {
        (*this) += that;
        return move(*this); // moving this is ok here
    }
}

答案 1 :(得分:5)

在rvalues上调用时,某些操作可以更有效,因此在*this的值类别上重载可以自动使用最有效的实现,例如。

struct Buffer
{
  std::string m_data;
public:
  std::string str() const& { return m_data; }        // copies data
  std::string str()&& { return std::move(m_data); }  // moves data
};

(此优化可以针对std::ostringstream进行,但尚未正式提出AFAIK。)

调用rvalues时有些操作没有意义,因此*this上的重载允许删除rvalue表单:

struct Foo
{
  void mutate()&;
  void mutate()&& = delete;
};

我还没有真正需要使用此功能,但是我可能会发现它有更多的用途,因为我关心的两个编译器都支持它。

答案 2 :(得分:1)

在我的编译器框架(即将发布的Sometime Soon™)中,您将诸如标记之类的信息传递到编译器对象中,然后调用finalize来指示流的结束。

在不调用finalize的情况下销毁对象会很糟糕,因为它不会清除所有输出。然而,析构函数无法完成finalize,因为它可以抛出异常,同样如果解析器已经中止,请finalize获取更多输出是错误的。

如果所有输入都已被另一个对象封装,那么将输入传递给rvalue编译器对象会很好。

pile< lexer, parser >( output_handler ).pass( open_file( "source.q" ) );

如果没有特别支持,这必须是不正确的,因为finalize没有被调用。界面不应该让用户做这样的事情。

首先要做的是排除finalize永远不会被调用的情况。如果使用如下的左值ref-qualifier调整原型,则不允许使用上面的示例:

void pass( input_file f ) & {
    process_the_file();
}

这样可以添加另一个重载来正确完成对象的空间。它是rvalue ref-qualified,因此只有在对象过期时调用它才会被选中。

void pass( input_file f ) && {
    pass( std::move( f ) ); // dispatch to lvalue case
    finalize();
}

现在用户几乎从不需要担心记住调用finalize,因为大多数编译器对象最终被实例化为临时对象。


请注意,这种事情并不适用于符合条件的成员。任何函数都可以为t &t &&分别设置重载。实际上目前实现pass的方式使用完美转发,然后回溯以确定正确的语义:

template< typename compiler, typename arg >
void pass( compiler && c, arg a ) {
    c.take_input( a );

    if ( ! std::is_reference< compiler >::value ) {
        c.finalize();
    }
}

有很多方法可以实现重载。实际上,不合格的成员函数在关注它们被调用的对象的类别(左值或右值)时是不常见的,并且不将该信息传递给函数。除隐式this之外的任何函数参数都必须说明其参数的类别。