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

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

5.8. Познакомьтесь с случаями некорректной работы прямой передачи

Одной из ярких звезд на небосклоне C++11 является прямая передача (perfect forwarding). Можно сказать, идеально прямая. Но, как говорит наука, даже пространство искривляется, так что есть идеальная прямота, а есть реальная. Прямая передача С++11 очень хороша, но достигает истинного совершенства, только если вы готовы игнорировать небольшие искривления. В данном разделе вы познакомитесь с этими маленькими искривлениями.

Перед тем как перейти к их изучению, стоит посмотреть, что мы подразумеваем под “прямой передачей”. Под передачей подразумевается, что одна функция передает своим параметры другой функции. Цель этого действия заключается в том, чтобы одна функция (которой передаются параметры) получила в точности те же объекты, которые переданы другой функции (которая выполняет передачу). Тем самым исключается передача параметров по значению, поскольку при этом выполняется копирование исходно переданных объектов; мы же хотим, чтобы передающая функция была способна работать с изначально переданными объектами. Указатели также исключаются, поскольку мы не хотим заставлять вызывающий код передавать указатели. Когда речь идет о передаче общего назначения, мы работаем с параметрами, представляющими собой ссылки.

Прямая передача означает, что мы передаем не просто объекты, но и их основные характеристики: их типы, являются они lvalue или rvalue, объявлены они как >const или >volatile. В сочетании с наблюдением о том, что мы будем иметь дело со ссылочными параметрами, это означает, что мы будем использовать универсальные ссылки (см. раздел 5.2), поскольку только параметры, являющиеся универсальными ссылками, хранят информацию о том, какие аргументы — являющиеся lvalue или rvalue — были им переданы.

Предположим, что у нас есть некоторая функция >f и мы хотели бы написать функцию (по правде говоря, шаблон функции), которая выполняет передачу ей. Ядро того, что нам надо, имеет следующий вид:

>template

>void fwd(T&& param)         // Принимает любой аргумент

>{

> f(std::forward(param)); // Передача аргумента в f

>}

Передающие функции по своей природе являются обобщенными. Шаблон >fwd, например, принимает аргумент любого типа, и он передает все, что бы ни получил. Логическим продолжением этой обобщенности являются передающие функции, являющиеся не просто шаблонами, а шаблонами с произвольным количеством аргументов (вариативными шаблонами (variadic templates)). Вариативная разновидность >fwd выглядит следующим образом:

>template

>void fwd(Ts&&... params)         // Принимает любые аргументы

>{

> f(std::forward(params)...); // Передача аргументов в f

>}

Эту разновидность вы встретите, помимо прочих мест, в функциях размещения стандартных контейнеров (см. раздел 8.2) и в фабричных функциях для интеллектуальных указателей, >std::make_shared и >std::make_unique (см. раздел 4.4).

Для заданной целевой функции >f и нашей передающей функции >fwd прямая передача завершается неудачей, если вызов функции >f с конкретным аргументом выполняет нечто одно, а вызов >fwd с теми же аргументами — нечто иное:

>f( expression );   // Если этот вызов выполняет что-то одно,

>fwd( expression ); // а этот - нечто иное, прямая передача

>                   // функцией fwd функции f неудачна

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

Инициализаторы в фигурных скобках

Предположим, что >f объявлена следующим образом:

>void f(const std::vector& v);

В этом случае вызов f с инициализаторами в фигурных скобках компилируется:

>f({ 1, 2, 3 }); // OK, "{1, 2, 3}" неявно преобразуется

>                // в std::vector

Однако передача того же инициализатора в фигурных скобках функции >fwd не компилируется:

>fwd({ 1, 2, 3 }); // Ошибка! Код не компилируется!

Дело в том, что применение инициализаторов в фигурных скобках — один из случаев, когда прямая передача терпит неудачу.

Все такие случаи отказов имеют одну и ту же причину. В непосредственном вызове >f (таком, как >f({1,2,3})) компиляторы видят аргументы, переданные в точке вызова, и видят типы параметров, объявленные функцией >f. Они сравнивают аргументы в точке вызова с объявлениями параметров на предмет совместимости и при необходимости выполняют неявное преобразование, чтобы вызов был успешным. В приведенном выше примере они генерируют временный объект типа >std::vector из >{1,2,3}, так что к параметру >v функции >f привязывается объект типа >std::vector.

При косвенном вызове >f с помощью шаблона передающей функции >fwd компиляторы больше не сравнивают аргументы, переданные в точке вызова >fwd, с объявлениями параметров в >f. Вместо этого они выводят типы аргументов, переданных в >fwd, и сравнивают выведенные типы с объявлениями параметров в >f. Прямая передача оказывается неудачной, если происходит что-то из следующего.

• Компиляторы неспособны вывести тип одного или нескольких параметров >fwd


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