Подтвердить что ты не робот

Каков предел для перегрузки функций-членов в ссылочных квалификаторах?

С++ 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-мишенями (т.е. добавить ссылочные квалификаторы & "к операторам присваивания), но было отклонено. Так что это был потенциальный случай использования, когда комитет решил не пойти с ним. Итак, опять же, что разумно использовать?

4b9b3361

Ответ 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: используйте его для предотвращения неправильного использования функции или для оптимизации.