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

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

>                     hours(1)),

>           _1,

>           seconds(30));

Если в этот момент лямбда-выражение не выглядит для вас гораздо более привлекательным, вам, пожалуй, надо сходить к окулисту.

При перегрузке >setAlarm возникают новые вопросы. Предположим, что перегрузка получает четвертый параметр, определяющий громкость звука:

>enum class Volume { Normal, Loud, LoudPlusPlus };


>void setAlarm(Time t, Sound s, Duration d, Volume v);

Лямбда-выражение продолжает работать, как и ранее, поскольку разрешение перегрузки выбирает трёхаргументную версию >setAlarm:

>auto setSoundL = // Как и ранее

> [](Sound s) {

>  using namespace std::chrono;

>  setAlarm(steady_clock::now() + 1h, // OK, вызывает

>           s,                        // setAlarm с тремя

>           30s);                     // аргументами

>};

Вызов же >std::bind теперь не компилируется:

>auto setSoundB =     // Ошибка! Какая из

> std::bind(setAlarm, // функций setAlarm?

>           std::bind(std::plus<>(),

>                     std::bind(steady_clock::now),

>                     1h),

>           _1,

>           30s);

Проблема в том, что у компиляторов нет способа определить, какая из двух функций >setAlarm должна быть передана в >std::bind. Все, что у них есть, — это имя функции, а одно имя приводит к неоднозначности.

Чтобы вызов >std::bind компилировался, >setAlarm должна быть приведена к корректному типу указателя на функцию:

>using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);


>auto setSoundB = // Теперь все в порядке

> std::bind(static_castsetAlarm),

>           std::bind(std::plus<>(),

>                     std::bind(steady_clock::now),

>                     1h),

>           _1,

>           30s);

Но это привносит еще одно различие между лямбда-выражениями и >std::bind. Внутри оператора вызова функции для >setSoundL (т.e. оператора вызова функции класса замыкания лямбда-выражения) вызов >setAlarm представляет собой обычный вызов функции, который может быть встроен компиляторами:

>setSoundL(Sound::Siren); // Тело setAlarm может быть встроено

Однако вызов >std::bind получает указатель на функцию >setAlarm, а это означает, что внутри оператора вызова функции >setSoundB (т.e. оператора вызова функции bind-объекта) имеет место вызов >setAlarm с помощью указателя на функцию. Компиляторы менее склонны к встраиванию вызовов функций, выполняемых через указатели, а это означает, что вызовы >setAlarm посредством >setSoundB будут встроены с куда меньшей вероятностью, чем вызовы посредством >setSoundL:

>setSoundB(Sound::Siren); // Тело setAlarm вряд ли будет встроено

Таким образом, вполне возможно, что применение лямбда-выражений приводит к генерации более быстрого кода, чем применение >std::bind.

Пример >setAlarm включает только простой вызов функции. Если вы хотите сделать что-то более сложное, то перевес в пользу лямбда-выражений только увеличится. Рассмотрим, например, такое лямбда-выражение С++14, которое выясняет, находится ли аргумент между минимальным (>lowVal) и максимальным (>highVal) значениями, где >lowVal и >highVal являются локальными переменными.

>auto betweenL =

> [lowVal, highVal]

> (const auto& val) // С++14

> { return lowVal <= val && val <= highVal; };

>std::bind может выразить то же самое, но эта конструкция является примером кода, безопасного только потому, что никто не в состоянии его понять:

>using namespace std::placeholders; // Как и ранее

>auto betweenВ =

> std::bind(std::logical_and<>(),   // С++14

>           std::bind(std::less_equal<>(), lowVal, _1),

>           std::bind(std::less_equal<>(), _1, highVal));

В С++11 мы должны указывать сравниваемые типы, и вызов >std::bind принимает следующий вид:

>auto betweenB =                    // Версия C++11

> std::bind(std::logical_and<bool>(),

>           std::bind(std::less_equal<int>(), lowVal, _1),

>           std::bind(std::less_equal<int>(), _1, highVal));

Конечно, в С++11 лямбда-выражение не может получать параметр >auto, так что здесь тоже надо указывать тип:

>auto betweenL = // Версия C++11

> [lowVal, highVal]

> (int val)

> { return lowVal <= val && val <= highVal; };

В любом случае, я надеюсь, все согласятся, что лямбда-версия не только более короткая, но и более понятная и легче поддерживаемая.

Ранее я отмечал, что для тех, у кого нет опыта работы с >std::bind, заполнители (например, >_1, >_2 и др.) выглядят магией. Но непонятно не только поведение заполнителей. Предположим, у нас есть функция для создания сжатых копий >Widget

>enum class CompLevel { Low, Normal, High }; // Уровень сжатия


>Widget compress(const Widget& w,            // Создание сжатой

>                CompLevel lev);             // копии w

и мы хотим создать функциональный объект, который позволяет нам указывать, насколько сильно должен быть сжат конкретный объект >Widget w. Представленное ниже применение >std::bind создает такой объект:

>Widget w;


>using namespace std::placeholders;


>auto compressRateB = std::bind(compress, w, _1);

Теперь, когда мы передаем >w в >std::bind, он должен храниться для последующего вызова >compress. Он сохраняется в объекте >compressRateB, но как именно — по значению или по ссылке! Это важно, потому что, если


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