Самостоятельное осмысление навеянное 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) на внутреннюю структуру класса, то имеет смысл использовать указание ссылочных квалификаторов, причём обоих: &
и &&
- второй будет уточнять поведение для временного объекта. Если уточнения по поведение не нужно, то использование ссылочных квалификаторов избыточно (но смотреть кейсы кейсы (в)
и (г)
) .