С. Мейерс - Эффективный и современный C++ (1114942), страница 50
Текст из файла (страница 50)
Из раздела 5.2 мы знаем, что, поскольку универсальная ссылка pa ram инициализируется с помощью lvalue, тип pa ram должен быть\vаluе-ссылкой, но как компилятор получит результат взятия выведенного типа для т и подстановки его в шаблон, который представляет собой конечную сигнатуру функции?void func (Widget& param) ;Ответ заключается в свертывании ссылок (reference collapsing). Да, вам запрещено объявлять ссылки на ссылки, но компиляторы могут создавать их в определенныхS .б. С вертывание ссы л ок203контекстах, среди которых - инстанцирование шаблонов.
Когда компиляторы генерируют ссылки на ссылки, свертывание ссылок определяет, что будет дальше.Существуют два вида ссылок (lvalue и rvalue), так что имеются четыре возможныекомбинации "ссылка на ссылку" (lvalue на lvalue, lvalue на rvalue, rvalue на lvalue и rvalueна гvalue). Если ссылка на ссылку возникает в контексте, где это разрешено (например,во время инстанцирования шаблона}, то ссылки сворачиваются в единственную ссылкусогласно следующему правилу:Если любая из ссылок является lvаluе-ссылкой, результат представляет собойlvalue-ccылкy.
В противном случае (т.е. когда обе ссылки являются rvаluе-ссылками)результат представляет собой гvаluе-ссылку.В нашем приведенном выше примере подстановка выведенного типа Widget& в шаблон func дает rvalue-ccылкy на lvalue-ccылкy, и правило свертки ссылок гласит, что результатом является lvalue-ccылкa.Свертывание ссылок является ключевой частью механизма, обеспечивающего работуstd : : forward. Как пояснялось в разделе 5.3, s t d : : forward применяется к параметрам,являющимся универсальными ссылками, так что обычно его применение имеет следующий вид:template<typename Т>void f ( T&& fParam)11 Некоторая работаsomeFunc (std : : forward<T> ( f Param) ) ; / / Передача fParam в1 1 someFuncПоскольку f Pa ram представляет собой универсальную ссылку, мы знаем, что параметр типа т будет кодировать информацию о том, являлся ли переданный f аргумент(т.е.
выражение, использованное для инициализации f Pa ram} lvalue или rvalue. Работаs t d : : forward заключается в приведении fPa ram (lvalue) к rvalue тогда и только тогда,когда Т гласит, что переданный в f аргумент был rvalue, т.е. если Т не является ссылочным типом.Вот как можно реализовать std : : forward, чтобы он выполнял описанные действия:t emplate<typename Т>1 1 В пространстве имен stdТ&& forward ( t ypenameremove_reference<T> : : type& param)return stat ic_cast<T&&> (param) ;Этот код не совсем отвечает стандарту (я опустил несколько деталей интерфейса), ноотличия не играют роли для понимания того, как ведет себя std : : forward.204Гnава 5.
Rvalue-ccыnки, семантика перемещений и прямая передачаПредположим, что аргумент, переданный f, является lvalue типа Widget . Тип Т будетвыведен как Widget &, а вызов s t d : : forward инстанцирует std : : forward<Widge t & >. Подстановка Widget & в реализацию s t d : : forward дает следующее:Wiclget& && forward ( typenameremove_reference<Wiclget&> : : type& param){ return static_cast<Widget& & & > ( param) ; )Свойство типа std : : remove_ reference<Widget & > : : t ype дает Widget (см. раздел 3.3),так что std : : forward превращается вWidget & && forward (Wiclget& param){ return static_cas t <Widget& &&> ( param) ;К возвращаемому типу и приведению также применяется сворачивание ссылок, и результат представляет собой последнюю версию std : : forward для вызова:Wiclget& forward (Widget& param){ return static_cast<Widget&> ( param) ;1 1 В пространстве11 имен stdКак можно видеть, когда в шаблон функции f передается аргумент lvalue,s t d : : forward инстанцируется для получения и возврата \vа\uе-ссылки.
Приведениевнутри st d : : forward не делает ничего, поскольку тип param уже представляет собойWidget &, так что приведение его к Widge t & ни на что не влияет. Таким образом, \vа\uеарrумент, переданный std : : forward, вернет \vа\uе-ссылку. По определению \vаluе-ссылкиявляются lvalue, так что передача lvalue в std : : forward приводит к возврату lvalue, каки предполагалось.Предположим теперь, что передаваемый f аргумент является rvalue типа W i dg e t .В этом случае выведенный тип параметра типа Т шаблона f будет просто Widget. Вызовstd : : forward внутри f, таким образом, будет представлять собой std : : forward<Widget>.Подстановка Widget вместо Т в реализации s t d : : forward дает следующее:Wiclget& & forward ( t ypenameremove_reference<Widget> : : type& param){ return static_cast<Widget& & > ( param) ; )Применение s t d : : remove_re ference к типу W idget, не являющемуся ссылкой, даеттот же тип, что и переданный (W idget ) , так что std : : forward превращается вWidget & & forward (Wiclget& param){ return static_cast<Widge t & & > (param) ;Здесь нет ссылок на ссылки, так что нет и свертывания ссылок, и это последняя инстанцированная версия s t d : : forward для этого вызова.Так как rvа\uе-ссылки, возвращаемые из функции, определены как rvalue, в этом случае std : : forward превратит параметр f Param (lvalue) функции f в rvalue.
Конечным результатом является то, что rvаluе-аргумент, переданный функции f, будет передан функции someFunc как rvalue, и это именно то, что и должно было произойти.S.6. С вертывание ссылок205Наличие в С++ 1 4 s t d : : r emove r e f e r e n c e t делает возможным реализоватьstd : : forward немного более лаконично:/ / С++ 1 4 ; вtemplate<typenarne Т>Т&& forward ( reшove reference_t<Т>& param) / / пространстве11 имен stdreturn static_cast<T&&> (param) ;Свертывание ссылок происходит в четырех контекстах. Первый и наиболее распространенный - инстанцирование шаблонов. Второй - генерация типов для переменных auto. Детали, по сути, те же, что и для шаблонов, поскольку вывод типа для аutопеременных, по сути, совпадает с выводом типов для шаблонов (см. раздел 1 .2). Рассмотрим еще раз пример, приводившийся ранее в данном разделе:template<typename Т>void func ( T & & pararn) ;Widget widgetFactory ( ) ; 1 1Widget w;1111func ( w ) ;11func ( widgetFactory ( ) ) ; 1 111Функция , возвращающая rvalueПеременная ( lvalue )Вызов функции с lvalue ; тип тпредставляет собой Widget&Вызов функции с rvalue; тип тпредставляет собой WidgetЭто можно имитировать в виде auto.
Объявлениеauto&& wlw;=инициализирует w l с помощью lvalue, выводя, таким образом, для auto тип Widget & .Подстановка W idget & вместо auto в объявление для w l дает код со ссылкой на ссылкуWidget& && wl=w;который после сворачивания ссылок принимает видWidget& wl=w;В результате w l представляет собой lvalue-ccылкy.С другой стороны, объявлениеauto&& w2=widgetFactory ( ) ;инициализирует w2 с помощью rvalue, приводя к тому, что для aut o выводится типWidget, не являющийся ссылкой. Подстановка Widget вместо auto даетWidget&& w2=widgetFactory ( ) ;Здесь нет ссылок на ссылки, так что процесс завершен; w2 представляет собой rvalueccылкy.206Гnа ва S .
Rvalue-ccыnки, семантика перемещений и прямая передачаТеперь мы в состоянии по-настоящему понять универсальные ссылки, введенныев разделе 5.2. Универсальная ссылка не является новой разновидностью ссылок, в действительности это rvalue-ccылкa в контексте, в котором выполняются два условия.•Вывод типа отличает lvalue от rvalue.•Происходит свертывание ссылок.lvalue типа т выводится как имеющее типв то время как rvalue типа т дает в качестве выведенного типа Т .Т&,Концепция универсальных ссылок полезна тем, что избавляет вас от необходимостираспознавать наличие контекстов сворачивания, мысленного вывода различных типовдля lvalue и rvalue и применения правила свертывания ссылок после мысленной подстановки выведенных типов в контексты, в которых они встречаются.Я говорил, что имеется четыре контекста, но мы рассмотрели только два из них: инстанцирование шаблонов и генерацию типов auto. Третьим является генерация и использование typede f и объявлений псевдонимов (см.
раздел 3.3). Если во время созданияили вычисления t ypede f возникают ссылки на ссылки, для их устранения применяется сворачивание ссылок. Предположим, например, что у нас есть шаблон класса Widgetс внедренным t ypedef для типа rvаluе-ссылкиtemplate<typenarne Т>class Widget {puЫic :typedef Т&& RvalueRefToT;};и предположим, что мы инстанцируем W idget с помощью типа lvаluе-ссылки:Widget<int&> w;Подстановка i nt & вместо Т в шаблоне W i dge t дает нам следующую конструкциюtypedef:typede f int& && RvalueRefToT;Сворачивание ссылок приводит этот код кtypedef int& RvalueRefToT;Теперь ясно, что имя, которое мы выбрали для t ypede f, вероятно, не настолько описательно, как мы надеялись: Rva l u e Re fToT представляет собой typedef для lvalue-ccьmкu,когда Widget инстанцируется типом lvаluе-ссылки.Последним контекстом, в котором имеет место сворачивание ссылок, является использование dec l t уре.
Если во время анализа типа, включающего decl t уре, возникаетссылка на ссылку, она устраняется сворачиванием ссылок. (Информацию о decltype вынайдете в разделе 1 .3.)S.б. С вертывание ссыпок207Следует запомнить•Сворачивание ссылок встречается в четырех контекстах: инстанцирование шаблона,генерация типа auto, создание и применение t ypede f и объявлений псевдонимов,и decl t ype.•Когда компиляторы генерируют ссылку на ссылку в контексте сворачивания ссылок,результатом становится единственная ссылка. Если любая из исходных ссылок является lvаluе-ссылкой, результатом будет lvalue-ccылкa; в противном случае это будетrvalue-ccылкa.•Универсальные ссылки представляют собой rvа\uе-ссылки в контекстах, в которыхвывод типов отличает lvalue от rvalue и происходит сворачивание ссылок.S.7.
Счита йте, что перемещающие операцииотсутствуют, дороrи иnи не испоnьзуютсяСемантика перемещения, пожалуй, самая главная возможность С++ 1 1 . Вам наверняка приходилось слышать, что "перемещение контейнеров теперь такое же дешевое, каки копирование указателей" или что "копирование временных объектов теперь настолькоэффективно, что избегать его равносильно преждевременной оптимизации': Понять такие настроения легко. Семантика перемещения действительно является очень важнойвозможностью.