标准还为成员函数调用引用限定符时,“*的rvalue引用”的最典型用例是什么?
顺便说一下,关于这种语言特征here有一个非常好的解释。
答案 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
之外的任何函数参数都必须说明其参数的类别。