Эффективный и современный С++. 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 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


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

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


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


Геймдизайн. Рецепты успеха лучших компьютерных игр от Super Mario и Doom до Assassin’s Creed и дальше

Что такое ГЕЙМДИЗАЙН? Это не код, графика или звук. Это не создание персонажей или раскрашивание игрового поля. Геймдизайн – это симулятор мечты, набор правил, благодаря которым игра оживает. Как создать игру, которую полюбят, от которой не смогут оторваться? Знаменитый геймдизайнер Тайнан Сильвестр на примере кейсов из самых популярных игр рассказывает как объединить эмоции и впечатления, игровую механику и мотивацию игроков. Познакомитесь с принципами дизайна, которыми пользуются ведущие студии мира! Создайте игровую механику, вызывающую эмоции и обеспечивающую разнообразие.


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

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


MFC и OpenGL

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


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

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


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

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