Hatred's Log Place

DON'T PANIC!

Mar 24, 2025 - 3 minute read - Programming

C++: заметки на полях про ref-qualified методы

Самостоятельное осмысление навеянное https://t.me/grokaemcpp/627.

Не задумывался над сценариями использования, хотя они есть с C++11.

Раньше была возможность использовать квалификатор const:

struct Foo {
  void method();        // (1)
  void method() const;  // (2)
};

Тут вроде логично, когда какой метод будет вызван.

C++11 добавил возможность пометить метод квалификатором ссылочности: ref-qualified:

  • &
  • const&
  • &&
  • const&& - хотят это сценарий очень странный.

Первое, что нужно отметить, что если хотя бы один из них определён, то он кофликтует с (1) и (2). Т.е. у вас или ref-qualified или non-ref-qualified:

struct Foo {
#if 0
  void method();        // (1)
  void method() const;  // (2)
#else
  void method() &;        // (3)
  void method() const&;   // (4)
  void method() &&;       // (5)
  void method() const&&;  // (6)
#endif
};

Второе: что и когда вызывается (или - ниже обозначает, два случая, для сравнения: когда использует и когда не используется ref-квалификация ):

Foo foo;
foo.method();           // (а) вызовется или (1) или (3), либо ОШИБКА, если перегрузка не указана

const Foo const_foo;
const_foo.method()      // (б) вызовется или (2) или (4), либо ОШИБКА, если перегрузка не указана

Foo{}.method();         // (в) вызовется или (1) или (5), либо (4), если перегрузка не указана
(const Foo){}.method(); // (г) вызовется или (2) или (6), либо (4), если перегрузка не указана

Тут логика такая: есть имя - вызовется &, нет имени - вызовется &&. И стоит внимательно посмотреть кейсы (в) и (г) для “старого” поведения и нового, но когда перегрузка по && не указана.

Третье: перегрузки могут быть помечены delete: удалённые функции тоже используются в поиске перегрузок и, если наиболее подходящая перегрузка удалена - будет ошибка компиляции.

Ну и для чего может быть нужно: оптимизации и защита. К примеру, если мы вызвали && метод, то мы точно знаем, что мы - временный объект. поэтому, мы можем сделать не просто возврат чего-то, а сделать этому std::move(), пометить метод delete и запретить вызваться или вернуть копию, а не ссылку или указатель.

Другой пример: висячие ссылки или другие отсылки к уничтоженным ресурсам.

struct Bar {
    std::string _val;
#if 0
    std::string_view get() const { return _val; }    // (7)
#else
    std::string_view get() const& { return _val; }   // (8)
    std::string      get() &&     { return _val; }   // (9) - можно и std::move(_val), но зависит от
endif
};

Тут вместо std::string_view может быть const std::string&, но со string_view можно проще отловить ситуацию с доступом к уничтоженному объекту:

// как помощиник
Bar request() {
  return Bar{};
}
...
auto sv = request().get(); // (д)
some_function(sv);         // (е)

Здесь без ref-qualified методов в (д) будет вызван (7), что приведёт в (е) и дальше по коду к обращению к уничтоженному объекту Bar::_val.

При использовании перегрузок по ref-qualified, будет вызван (9) и мы вернём копию строки (или более оптимально, если через std::move(_val)).

Отсюда, имхо, основное применение: если возвращаем некий референс (ссылку, view/span) на внутреннюю структуру класса, то имеет смысл использовать указание ссылочных квалификаторов, причём обоих: & и && - второй будет уточнять поведение для временного объекта. Если уточнения по поведение не нужно, то использование ссылочных квалификаторов избыточно (но смотреть кейсы кейсы (в) и (г)) .

Tags: Programming CXX C++ C++11

LocalSend: отправка файлов между устройсвами внутри LAN