Универсальные ссылки
Вообще говоря, вся эта серия постов началась с просьбы нашего подписчика Сергея Нефедова объяснить зачем нужны универсальные ссылки. Дождались! 🤩
В предыдущей статье я сделал акцент:
Тип rvalue reference задаётся с помощью && перед именем класса.
ОДНО БОЛЬШОЕ НО! Вместо имени класса может быть установлен параметр-тип шаблона:
Ожидается, что из него будет выведен тип
В своё время Scott Meyers, придумал такой термин как универсальные ссылки, чтобы объяснить некоторые тонкости языка. Рассмотрим на примере вышеупомянутой
Оба вызова функции
Универсальная ссылка (т.н. universal reference) — это переменная или параметр, которая имеет тип T&& для выведенного типа T. Из неё будет выведен тип rvalue reference, либо lvalue reference. Это так же касается auto переменных, т.к. их тип тоже выводится.
Расставляем точки над i вместе со Scott Meyers:
В соответствии с этим маленьким нюансом поведение может меняться внутри функции
Я немного изменил предыдущий пример: https://compiler-explorer.com/z/EzddYhjdv. В зависимости от выведенного типа, строка будет либо скопирована, либо перемещена. Соответственно, в области видимости функции
Причем, это не работает, если
Пример: https://compiler-explorer.com/z/We4qzG5xG
Получается, что в универсальные ссылки заложен дуализм поведения. Зачем же так было сделано? А за тем, что существуют
Как мы видим, разные аргументы вызова
Кстати, если не знать и не пытаться в эти тонкости, то можно вполне спокойно использовать стандартные структуры. Если говорить с натяжкой, то можно, конечно, сказать, что такая универсальность может снижать порог вхождения в C++. Не знаешь — пишешь просто рабочий код, а знаешь — пишешь ещё и эффективный.
Другое дело, непонятно, почему нельзя было для универсальных ссылок сделать отдельный синтаксис? Например, добавить
Я думаю, что нам еще нужны посты на разбор этой темы, чтобы это в голове уложилось. А пока будем развивать тему в сторону move семантики. Не забываем об исключениях в перемещающем конструкторе, а так же про оптимизации RVO/NRVO.
#cppcore #memory #algorithm #hardcore
Вообще говоря, вся эта серия постов началась с просьбы нашего подписчика Сергея Нефедова объяснить зачем нужны универсальные ссылки. Дождались! 🤩
В предыдущей статье я сделал акцент:
Тип rvalue reference задаётся с помощью && перед именем класса.
ОДНО БОЛЬШОЕ НО! Вместо имени класса может быть установлен параметр-тип шаблона:
template<typename T>
void foo(T &&message)
{
...
}
Ожидается, что из него будет выведен тип
rvalue reference
, но это не всегда так. Такие ссылки позволяют с одной стороны определить поведения для работы с xvalue
, а с другой, неожиданно, для lvalue
.В своё время Scott Meyers, придумал такой термин как универсальные ссылки, чтобы объяснить некоторые тонкости языка. Рассмотрим на примере вышеупомянутой
foo
:std::string str = "blah blah blah";
// Передает lvalue
foo(str);
// Передает xvalue (rvalue reference)
foo(std::move(str));
Оба вызова функции
foo
будут корректны, если не брать во внимание реализацию foo
. Живой примерУниверсальная ссылка (т.н. universal reference) — это переменная или параметр, которая имеет тип T&& для выведенного типа T. Из неё будет выведен тип rvalue reference, либо lvalue reference. Это так же касается auto переменных, т.к. их тип тоже выводится.
Расставляем точки над i вместе со Scott Meyers:
Widget &&var1 = someWidget;
// ~~^~~
// rvalue reference
auto &&var2 = var1;
// ~~^~~
// universal reference
template<typename T>
void f(std::vector<T> &¶m);
// ~~^~~
// rvalue reference
template<typename T>
void f(T &¶m);
// ~~^~~
// universal reference
В соответствии с этим маленьким нюансом поведение может меняться внутри функции
foo
. Банально, можно накодить тормозящее копирование вместо производительной передачи ресурса.Я немного изменил предыдущий пример: https://compiler-explorer.com/z/EzddYhjdv. В зависимости от выведенного типа, строка будет либо скопирована, либо перемещена. Соответственно, в области видимости функции
main
объект либо выводит текст, либо нет (т.к. ресурс был передан другому объекту внутри foo
).Причем, это не работает, если
T
— параметр-тип шаблонного класса:template<class T>
class mycontainer
{
public:
void push_back(T &&other) { ... }
~~~^~~~
rvalue reference
...
};
Пример: https://compiler-explorer.com/z/We4qzG5xG
Получается, что в универсальные ссылки заложен дуализм поведения. Зачем же так было сделано? А за тем, что существуют
template parameter pack
:template<class... Ts>
void foo(Ts... args)
{
bar(args...);
}
foo(std::move(string), value);
~~~~^~~~ ~~^~~~
xvalue lvalue
Как мы видим, разные аргументы вызова
foo
могут относиться к разным категориям выражений.Кстати, если не знать и не пытаться в эти тонкости, то можно вполне спокойно использовать стандартные структуры. Если говорить с натяжкой, то можно, конечно, сказать, что такая универсальность может снижать порог вхождения в C++. Не знаешь — пишешь просто рабочий код, а знаешь — пишешь ещё и эффективный.
Другое дело, непонятно, почему нельзя было для универсальных ссылок сделать отдельный синтаксис? Например, добавить
T &&&
. Т.к. сейчас это рушит всю концептуальную целостность системы типов. Если это планировалось как гибкий механизм, то он граничит с полной дезориентацией разработчиков 😊Я думаю, что нам еще нужны посты на разбор этой темы, чтобы это в голове уложилось. А пока будем развивать тему в сторону move семантики. Не забываем об исключениях в перемещающем конструкторе, а так же про оптимизации RVO/NRVO.
#cppcore #memory #algorithm #hardcore