Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [88]

Шрифт
Интервал

для вызова:

>Widget& forward(Widget& param)          // В пространстве

>{ return static_cast<Widget&>(param); } // имен std

Как можно видеть, когда в шаблон функции >f передается аргумент lvalue, >std::forward инстанцируется для получения и возврата lvalue-ссылки. Приведение внутри >std::forward не делает ничего, поскольку тип >param уже представляет собой >Widget&, так что приведение его к >Widget& ни на что не влияет. Таким образом, lvalue-аргумент, переданный >std::forward, вернет lvalue-ссылку. По определению lvalue-ссылки являются lvalue, так что передача lvalue в >std::forward приводит к возврату lvalue, как и предполагалось.

Предположим теперь, что передаваемый >f аргумент является rvalue типа >Widget. В этом случае выведенный тип параметра типа >T шаблона >f будет просто >Widget. Вызов >std::forward внутри >f, таким образом, будет представлять собой >std::forward. Подстановка >Widget вместо >T в реализации >std::forward дает следующее:

>Widget&& forward(typename

>                 remove_reference<Widget>::type& param)

>{ return static_cast<Widget&&>(param); }

Применение >std::remove_reference к типу >Widget, не являющемуся ссылкой, дает тот же тип, что и переданный (>Widget), так что >std::forward превращается в

>Widget&& forward(Widget& param)

>{ return static_cast(param); }

Здесь нет ссылок на ссылки, так что нет и свертывания ссылок, и это последняя инстанцированная версия >std::forward для этого вызова.

Так как rvalue-ссылки, возвращаемые из функции, определены как rvalue, в этом случае >std::forward превратит параметр >fParam (lvalue) функции >f в rvalue. Конечным результатом является то, что rvalue-аргумент, переданный функции >f, будет передан функции >someFunc как rvalue, и это именно то, что и должно было произойти.

Наличие в С++14 >std::remove_reference_t делает возможным реализовать >std::forward немного более лаконично:

>template                      // С++14; в

>T&& forward(remove_reference_t& param) // пространстве

>{                                         // имен std

> return static_cast(param);

>}

Свертывание ссылок происходит в четырех контекстах. Первый и наиболее распространенный — инстанцирование шаблонов. Второй — генерация типов для переменных >auto. Детали, по сути, те же, что и для шаблонов, поскольку вывод типа для >auto-переменных, по сути, совпадает с выводом типов для шаблонов (см. раздел 1.2). Рассмотрим еще раз пример, приводившийся ранее в данном разделе:

>template void func(T&& param);


>Widget widgetFactory(); // Функция, возвращающая rvalue

>Widget w;               // Переменная (lvalue)

>func(w);                // Вызов функции с lvalue; тип T

>                        // представляет собой Widget&

>func(widgetFactory());  // Вызов функции с rvalue; тип T

>                        // представляет собой Widget

Это можно имитировать в виде >auto. Объявление

>auto&& w1 = w;

инициализирует >w1 с помощью lvalue, выводя, таким образом, для >auto тип >Widget&. Подстановка >Widget& вместо >auto в объявление для >w1 дает код со ссылкой на ссылку

>Widget& && w1 = w;

который после сворачивания ссылок принимает вид

>Widget& w1 = w;

В результате w1 представляет собой lvalue-ссылку. С другой стороны, объявление

>auto&& w2 = widgetFactory();

инициализирует >w2 с помощью rvalue, приводя к тому, что для >auto выводится тип >Widget, не являющийся ссылкой. Подстановка >Widget вместо >auto дает

>Widget&& w2 = widgetFactory();

Здесь нет ссылок на ссылки, так что процесс завершен; >w2 представляет собой rvalue-ссылку.

Теперь мы в состоянии по-настоящему понять универсальные ссылки, введенные в разделе 5.2. Универсальная ссылка не является новой разновидностью ссылок, в действительности это rvalue-ссылка в контексте, в котором выполняются два условия.

• Вывод типа отличает lvalue от rvalue. lvalue типа >T выводится как имеющее тип >T&, в то время как rvalue типа >T дает в качестве выведенного типа >T.

• Происходит свертывание ссылок.

Концепция универсальных ссылок полезна тем, что избавляет вас от необходимости распознавать наличие контекстов сворачивания, мысленного вывода различных типов для lvalue и rvalue и применения правила свертывания ссылок после мысленной подстановки выведенных типов в контексты, в которых они встречаются.

Я говорил, что имеется четыре контекста, но мы рассмотрели только два из них: инстанцирование шаблонов и генерацию типов >auto. Третьим является генерация и использование >typedef и объявлений псевдонимов (см. раздел 3.3). Если во время создания или вычисления >typedef возникают ссылки на ссылки, для их устранения применяется сворачивание ссылок. Предположим, например, что у нас есть шаблон класса >Widget с внедренным >typedef для типа rvalue-ссылки

>template

>class Widget {

>public:

> typedef T&& RvalueRefToT;


>};

и предположим, что мы инстанцируем >Widget с помощью типа lvalue-ссылки:

>Widget<int&> w;

Подстановка >int& вместо >T в шаблоне >Widget дает нам следующую конструкцию >typedef:

>typedef int& && RvalueRefToT;

Сворачивание ссылок приводит этот код к

>typedef int& RvalueRefToT;

Теперь ясно, что имя, которое мы выбрали для >typedef, вероятно, не настолько описательно, как мы надеялись:


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.