Что такое "ссылки rvalue для этого"?
Каковы наиболее типичные варианты использования "ссылок на rvalue для * this", которые стандарт также вызывает ссылочные квалификаторы для функций-членов?
Кстати, есть действительно хорошее объяснение этой языковой функции здесь.
Ответы
Ответ 1
При вызове каждая функция-член имеет неявный параметр объекта, который *this
ссылается.
Итак, (а) эти нормальные перегрузки:
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
}
}
Ответ 2
Некоторые операции могут быть более эффективными при вызове 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.)
Некоторые операции не имеют смысла вызывать значения r, поэтому перегрузка на *this
позволяет удалить форму rvalue:
struct Foo
{
void mutate()&;
void mutate()&& = delete;
};
Мне еще не нужно было использовать эту функцию, но, возможно, теперь я найду для нее больше возможностей, чем два компилятора, о которых я забочусь о поддержке.
Ответ 3
В моей компоновке компилятора (который будет выпущен Sometime Soon ™) вы передаете элементы информации, такие как токены, в объект компилятора, затем вызовите finalize
, чтобы указать конец потока.
Было бы плохо уничтожить объект, не вызывая finalize
, потому что он не выведет весь его вывод. Тем не менее finalize
не может быть выполнено деструктором, потому что он может вызывать исключение, а также неправильно спрашивать finalize
для большего вывода, если парсер уже прерывается.
В случае, когда весь вход уже инкапсулирован другим объектом, приятно передать вход в объект компилятора rvalue.
pile< lexer, parser >( output_handler ).pass( open_file( "source.q" ) );
Без специальной поддержки это должно быть неверно, потому что finalize
не вызывается. Интерфейс не должен позволять пользователю делать что-то вообще.
Первое, что нужно сделать, это исключить случай, когда finalize
никогда не вызывается. Вышеприведенный пример запрещен, если прототип настроен с помощью l-ref ref-qualifier следующим образом:
void pass( input_file f ) & {
process_the_file();
}
Это дает возможность добавить еще одну перегрузку, которая правильно завершает объект. Это rvalue ref-qual, поэтому он выбирается только в том случае, если вызывается на истечении объекта.
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();
}
}
Существует множество способов подхода к перегрузке. Фактически, неквалифицированные функции-члены необычны в том, что они не заботятся о категории (lvalue или rvalue) объекта, на который они вызваны, и не передают эту информацию в функцию. Любой параметр функции, кроме неявного this
, должен сказать что-то о категории его аргумента.