Каков предел для перегрузки функций-членов в ссылочных квалификаторах?
С++ 11 позволяет перегрузить функции-члены на основе эталонных квалификаторов:
class Foo {
public:
void f() &; // for when *this is an lvalue
void f() &&; // for when *this is an rvalue
};
Foo obj;
obj.f(); // calls lvalue overload
std::move(obj).f(); // calls rvalue overload
Я понимаю, как это работает, но что для этого используется?
Я вижу, что N2819 предлагал ограничить большинство операторов присваивания в стандартной библиотеке lvalue-мишенями (т.е. добавить ссылочные квалификаторы &
"к операторам присваивания), но было отклонено. Так что это был потенциальный случай использования, когда комитет решил не пойти с ним. Итак, опять же, что разумно использовать?
Ответы
Ответ 1
В классе, который предоставляет ссылки-getters, перегрузка ref-qualifier может активировать семантику перемещения при извлечении из rvalue. Например:.
class some_class {
huge_heavy_class hhc;
public:
huge_heavy_class& get() & {
return hhc;
}
huge_heavy_class const& get() const& {
return hhc;
}
huge_heavy_class&& get() && {
return std::move(hhc);
}
};
some_class factory();
auto hhc = factory().get();
Это выглядит как много усилий, чтобы инвестировать только в более короткий синтаксис
auto hhc = factory().get();
имеют тот же эффект, что и
auto hhc = std::move(factory().get());
EDIT: я нашел исходную рекламную статью , она содержит три мотивирующих примера:
- Ограничение
operator =
до lvalues (ответ TemplateRex)
- Включение перемещения для участников (в основном этот ответ)
- Ограничение
operator &
до lvalues. Я полагаю, это разумно, чтобы гарантировать, что "pointee" с большей вероятностью будет живым, когда "указатель" в конечном итоге будет разыменован:
struct S {
T operator &() &;
};
int main() {
S foo;
auto p1 = &foo; // Ok
auto p2 = &S(); // Error
}
Не могу сказать, что я когда-либо использовал перегрузку operator&
.
Ответ 2
Один вариант использования запретить назначение временным файлам
// can only be used with lvalues
T& operator*=(T const& other) & { /* ... */ return *this; }
// not possible to do (a * b) = c;
T operator*(T const& lhs, T const& rhs) { return lhs *= rhs; }
тогда как использование ссылочного квалификатора не даст вам выбора между двумя неудачами
T operator*(T const& lhs, T const& rhs); // can be used on rvalues
const T operator*(T const& lhs, T const& rhs); // inhibits move semantics
Первый выбор позволяет переносить семантику, но действует по-разному в пользовательских типах, чем на встроенных (не делает так, как это делают int). Второй вариант остановил бы настройку, но исключил бы семантику перемещения (возможная производительность, например, для умножения матрицы).
Связи @dyp в комментариях также содержат расширенное обсуждение использования другой (&&
) перегрузки, которая может быть полезна, если вы хотите назначить ссылки (либо lvalue, либо rvalue).
Ответ 3
Если f() нуждается в Foo temp, который является копией этого и изменен, вы можете изменить temp this
, а не иначе
Ответ 4
С одной стороны, вы можете использовать их для предотвращения функций, которые семантически бессмысленны для вызова вызываемых временных рядов, таких как operator=
или функций, которые мутируют внутреннее состояние и возвращают void
, добавив &
в качестве эталонный квалификатор.
С другой стороны, вы можете использовать его для оптимизации, например, перемещение члена из объекта в качестве возвращаемого значения, когда у вас есть ссылка на rvalue, например, функция getName
может возвращать либо std::string const&
, либо std::string&&
в зависимости от ссылочного квалификатора.
Другим вариантом использования могут быть операторы и функции, которые возвращают ссылку на исходный объект, такой как Foo& operator+=(Foo&)
, который может быть специализирован для возврата ссылки rvalue, делая результат подвижным, что опять-таки будет оптимизацией.
TL; DR: используйте его для предотвращения неправильного использования функции или для оптимизации.