“rvalue references for *this”(标准中也称成员函数的引用限定符)最典型的使用案例是什么?
顺便提一下,有一个非常好的关于这个语言特性的解释在这里。
“rvalue references for *this”(标准中也称成员函数的引用限定符)最典型的使用案例是什么?
顺便提一下,有一个非常好的关于这个语言特性的解释在这里。
每个成员函数在调用时,都有一个隐式对象参数,*this
引用它。
因此,这些普通函数重载:
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
}
}
void f(T&)
,因此在这里的用例与 T::f() &
相同,涉及隐式对象参数。我的主要观点是注意普通函数参数和隐式对象参数之间的正交关系 - 因此,对于普通函数参数适用的值类别和重载选择的一般理解可以直接重用为成员函数隐式对象参数(仅有不值得一提的小差异)。 - Andrew Tomazosoperator +=
的实际操作,但在某些情况下可能更有效率。已更改,谢谢。 - Andrew Tomazos*this
到一个不会扩展其生命周期的引用上。例如const C&c = C() + C(); c.foo();
,在调用foo之前,c
已被销毁。 - Andrew Tomazos当针对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
};
struct Foo
{
void mutate()&;
void mutate()&& = delete;
};
我实际上还没有需要使用这个功能,但是现在我可能会发现更多的用途,因为我关心的两个编译器都支持它。
Foo::mutate()
可以针对rvalue进行操作... - Andrew Tomazosmutate() no-ref-qualifier
和mutate()&&
之间产生歧义。这是您的意图吗? - Andrew Tomazos&
,已经修复了。 - Jonathan Wakelyvoid mutate()&& = delete;
是不必要的,因为一个 rvalue 不会绑定到 void mutate()&
。 - Andrew Tomazosfinalize
表示流的结束。不调用finalize
而销毁对象是不好的,因为它不能清除所有输出。但是,析构函数不能执行finalize
,因为它可能会抛出异常,如果解析器已经中止,则请求finalize
获取更多输出也是错误的。当所有输入已经由另一个对象封装时,在rvalue编译器对象中传递输入很好。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
,因为大多数编译器对象最终都被实例化为临时对象。
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
之外,任何函数参数都必须说明其参数的类别。
operator=
。例如T& operator=(T const&);
将防止T() = T();
编译通过。 - R. Martinho Fernandes