24

What are the most typical use cases of "rvalue references for *this" which the standard also calls reference qualifiers for member functions?

By the way, there is a really good explanation about this language feature here.

4

3 回答 3

21

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

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

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

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

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

当被调用时x.f()- (a) 和 (b) 都以相似的生存能力和排名调度。

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

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
    }
}
于 2013-07-08T10:21:00.620 回答
5

某些操作在右值上调用时可能更有效,因此在值类别上重载*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。)

某些操作调用右值没有意义,因此重载 on*this允许删除右值形式:

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

我实际上还不需要使用这个特性,但也许我会发现它的更多用途,因为我关心的两个编译器都支持它。

于 2013-07-08T11:39:35.230 回答
1

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

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

在所有输入已经被另一个对象封装的情况下,最好将输入传递给右值编译器对象。

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

如果没有特殊支持,这一定是不正确的,因为finalize没有被调用。界面根本不应该让用户做这样的事情。

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

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

这为添加另一个正确完成对象的重载提供了空间。它是右值引用限定的,因此只有在对即将到期的对象调用时才会选择它。

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

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


请注意,这类事情并非特定于具有 ref 资格的成员。任何函数都可以对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必须说明其参数的类别。

于 2013-07-08T13:28:00.753 回答