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

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

>template

>void func(T&& param);

выведенный параметр шаблона >T будет включать информацию о том, был ли переданный в >param аргумент lvalue или rvalue.

Механизм этого кодирования прост. Если в качестве аргумента передается lvalue, >T выводится как lvalue-ссылка. При передаче rvalue вывод типа приводит к тому, что >T не является ссылкой. (Обратите внимание: lvalue кодируются как lvalue-ссылки, но rvalue кодируются как не ссылки.) Следовательно:

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

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

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

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

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

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

В обоих вызовах >func передается >Widget, но так как один >Widget является lvalue, а второй представляет собой rvalue, для параметра шаблона >T выводятся разные типы. Это, как вы вскоре увидите, и определяет, чем становятся универсальные ссылки: rvalue- или lvalue-ссылками; а кроме того, это механизм, лежащий в основе работы >std::forward.

Прежде чем более внимательно рассмотреть >std::forward и универсальные ссылки, мы должны заметить, что ссылка на ссылку в С++ не существует. Попытайтесь объявить ее, и компилятор вынесет вам строгий выговор:

>int x;

>…

>auto& & rx = x; // Ошибка! Объявлять ссылки на ссылки нельзя

Но рассмотрим, что произойдет, если передать lvalue шаблону функции, принимающему универсальную ссылку:

>template

>void func(T&& param); // Как и ранее

>func(w);              // Вызов func с lvalue;

>                      // T выводится как Widget&

Если мы возьмем тип, выведенный для >T (т.e. >Widget&) и используем его для инстанцирования шаблона, то получим следующее:

>void func(Widget& && param);

Ссылка на ссылку! Но компиляторы не возражают. Из раздела 5.2 мы знаем, что, поскольку универсальная ссылка param инициализируется с помощью lvalue, тип >param должен быть lvalue-ссылкой, но как компилятор получит результат взятия выведенного типа T и подстановки его в шаблон, который представляет собой конечную сигнатуру функции?

>void func(Widget& param);

Ответ заключается в свертывании ссылок (reference collapsing). Да, вам запрещено объявлять ссылки на ссылки, но компиляторы могут создавать их в определенных контекстах, среди которых — инстанцирование шаблонов. Когда компиляторы генерируют ссылки на ссылки, свертывание ссылок определяет, что будет дальше.

Существуют два вида ссылок (lvalue и rvalue), так что имеются четыре возможные комбинации “ссылка на ссылку” (lvalue на lvalue, lvalue на rvalue, rvalue на lvalue и rvalue на rvalue). Если ссылка на ссылку возникает в контексте, где это разрешено (например, во время инстанцирования шаблона), то ссылки сворачиваются в единственную ссылку согласно следующему правилу:

Если любая из ссылок является lvalue-ссылкой, результат представляет собой lvalue-ссылку. В противном случае (т.e. когда обе ссылки являются rvalue-ссылками) результат представляет собой rvalue-ссылку.

В нашем приведенном выше примере подстановка выведенного типа >Widget& в шаблон >func дает rvalue-ссылку на lvalue-ссылку, и правило свертки ссылок гласит, что результатом является lvalue-ccылкa.

Свертывание ссылок является ключевой частью механизма, обеспечивающего работу >std::forward. Как пояснялось в разделе 5.3, >std::forward применяется к параметрам, являющимся универсальными ссылками, так что обычно его применение имеет следующий вид:

>template

>void f(T&& fParam) {

> …                                  // Некоторая работа

> someFunc(std::forward(fParam)); // Передача fParam в

>}                                   // someFunc

Поскольку >fParam представляет собой универсальную ссылку, мы знаем, что параметр типа >T будет кодировать информацию о том, являлся ли переданный >f аргумент (т.e. выражение, использованное для инициализации >fParam) lvalue или rvalue. Работа >std::forward заключается в приведении >fParam (lvalue) к rvalue тогда и только тогда, когда >T гласит, что переданный в >f аргумент был rvalue, т.e. если >T не является ссылочным типом.

Вот как можно реализовать >std::forward, чтобы он выполнял описанные действия:

>template // В пространстве имен std

>T&& forward(typename

>            remove_reference::type& param) {

> return static_cast(param);

>}

Этот код не совсем отвечает стандарту (я опустил несколько деталей интерфейса), но отличия не играют роли для понимания того, как ведет себя >std::forward.

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

>Widget& && forward(typename

>                   remove_reference<Widget&>::type& param)

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

Свойство типа >std::remove_reference::type дает >Widget (см. раздел 3.3), так что >std::forward превращается в

>Widget& && forward(Widget& param)

>{ return static_cast(param); }

К возвращаемому типу и приведению также применяется сворачивание ссылок, и результат представляет собой последнюю версию


Еще от автора Скотт Мейерс
Эффективное использование 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 так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.