С. Мейерс - Эффективный и современный C++ (1114942), страница 58
Текст из файла (страница 58)
Например, предположим, что у нас есть функция для настройки будильника:11 Тип для момента времени ( см . синтаксис в разделе 3 . 3 )usiпg Timestd: : chroпo : : steady_clock : : time_point ;// См . "enum class" в разделе 3 . 4eпum class Sound { Веер, Sireп, Whistle } ;// Тип для продолжительности промежутка времениusiпg Duratioп=std: : chroпo : : steady_clock: : duration;11 В момент t издать звук s продолжительностью dvoid setAlarm (Time t , Sound s , Duration d) ;Далее предположим, что в некоторой точке программы мы определили, что хотим, чтобы будильник был отключен в течение часа, после чего подал сигнал продолжительностью6.4.
Предпочитайте лямбда-выражения применению std::Ьind23730 с. Сам звук остается неопределенным. Мы можем написать лямбда-выражение, котороеизменяет интерфейс setAl ann так, что необходимо указать только звук:11 setSoundL ( " 1 " означает "лямбда-выражение " ) - функциональный11 объект, позволяющий указать сигнал будильника , который должен11 звучать через час в течение 30 сauto setSoundL[ ] ( Sound s )={11 Делает доступными компоненты std : : chronousing namespace std : : chrono;setAlarш (steady clock : : now ( ) +hours (l ) , / / Будильник через_11 час, звучитs,11 30 секундseconds (ЗO) ) ;f;Я выделил вызов setAlann в лямбда-выражении.
Он выглядит, как обычный вызов функции, и даже читатель с малым опытом в лямбда-выражениях может понять, что переданныйлямбда-выражению параметр s передается в качестве аргумента функции setAlarrn.В С++ 1 4 этот код можно упростить, используя стандартные суффиксы для секунд (s),миллисекунд (ms), часов (h) и друтих единиц, основанных на поддержке пользовательских литералов в С++ 14.
Эти суффиксы определены в пространстве имен std : : l iterals,так что приведенный выше код переписывается какauto setSoundL[ ] ( Sound s )={using namespace std : : chrono;11 Суффиксы С++ 1 4using namespace std: : literals ;setAlann ( steady_clock : : now ( ) +s,lh, / / С++ 1 4 , смысл11 тот же , что// И ВЬП!JеЗОs ) ;1;Наша первая попытка написать соответствующий вызов std : : Ьind приведена ниже.Она содержит ошибки, которые мы вскоре исправим, но главное - что правильный кодболее сложен, и даже эта упрощенная версия приводит к ряду важных вопросов:using namespace std: : chrono;using namespace std : : litera l s ;using namespace std : : placeholders ;auto setSoundВ =std : : Ьind ( setAlarm,steady_clock : : now ( ) + lh,1,30s) ;238Глава 6. Лямбда-выражения11 Как и ранее11 Необходимо дпя " 1 "1 1 "В" означает "bind"//Оимбка ! См .нижеЯ хотел бы выделить вызов setAlarm, как делал это в лямбда-выражении, но здесьнет вызова, который можно было бы выделить.
Читатели этого кода просто должнызнать, что вызов set SoundB приводит к вызову setAlarm со временем и продолжительностью, указанными в вызове s t d : : Ьi nd. Для непосвященных заполнитель " l" выглядит магически, но даже знающие читатели должны в уме отобразить число в заполнителена позицию в списке параметров s t d : : Ьind, чтобы понять, что первый аргумент вызова setSoundB передается в setAlarm в качестве второго аргумента. Тип этого аргументав вызове std : : Ьind не определен, так что читатели должны проконсультироваться с объявлением setAlarm, чтобы выяснить, какой аргумент передается в set SoundB.Но, как я уже говорил, этот код не совсем верен. В лямбда-выражении очевидно,что выражение " s t e ady_c l o c k : : now ( ) + l h" представляет собой аргумент s e t A l a rm.Оно будет вычислено при вызове s e t A l a rm.
Это имеет смысл: мы хотим, чтобы будильник заработал через час после вызова set A l arm. Однако в вызове std : : Ь i nd выражение " st eady_clock : : now ( ) +lh" передается в качестве аргумента в std : : Ьi nd, а нев setAlarm. Это означает, что выражение будет вычислено при вызове std : : Ьind и полученное время будет храниться в сгенерированном Ьind-объекте. В результате будильниксработает через час после вызова s t d : : b i nd, а не через час после вызова setAlarm!Решение данной проблемы требует указания для st d : : Ь i nd отложить вычислениевыражения до вызова setAlarm, и сделать это можно с помощью вложения еще двух вызовов s td : : Ьind в первый:auto setSoundВstd: : bind ( setAlarm,=std : : bind (std: : plus<> O ,std: :Ьind (steady_clock : : now) ,lh) '1,ЗО s ) ;Если вы знакомы с шаблоном s t d : : p l u s из С++98, вас может удивить то, чтомежду угловыми скобками не указан тип, т.е.
что код содержит " st d : : p l u s <>'', а не"st d : : plus < t yp e > ". В C++ l4 аргумент типа шаблона для шаблонов стандартных операторов в общем случае может быть опущен, так что у нас нет необходимости указыватьего здесь. С++ l l такой возможности не предоставляет, так что в С++ l l эквивалентныйлямбда-выражению std : : Ьind имеет следующий вид:using namespace std : : chrono ;/ / Как и ранееusing namespace s t d : : placeholde r s ;auto setSoundBstd : : bind ( setAlarm,std : : Ьind ( std: : plus< steady clock : : time_JIOint> ( )std: : Ьind ( steady_clock : : now ) ,hours ( l ) ) ,=_,6.4.
Предпочитайте л ямбда-выражения применению std::Ьiпd2391,seconds ( 30 ) ) ;Если в этот момент лямбда-выражение не выглядит для вас гораздо более привлекательным, вам, пожалуй, надо сходить к окулисту.При перегрузке setAlarm возникают новые вопросы. Предположим, что перегрузкаполучает четвертый параметр, определяющий громкость звука:enum class Volume { Nonnal , Loud, LoudPlusPlus } ;void setAlarm (Time t, Sound s , Duration d, Volume v) ;Лямбда-выражение продолжает работать, как и ранее, поскольку разрешение перегрузки выбирает трехарrументную версию setAlarm:auto setSoundL = / / Как и ранее[ ] ( Sound s ){using narnespace std : : chrono;setAlann ( steady_clock : : now ( ) + lh, // ОК, вызывает/ / setAlarm с тремяs,/ / аргументамиЗОs ) ;};Вызов же std : : Ьi nd теперь не компилируется:/ / Ошибка ! Какая изauto setSoundВ =std : : bind ( setAlarm,/ / функций setAlarm?std : : Ьind ( std : : plus<> ( ) ,std: : Ьind ( steady_clock : : now) ,lh) ,1,ЗОs ) ;Проблема в том, что у компиляторов нет способа определить, какая из двух функцийsetAl a rm должна быть передана в s t d : : Ьi nd.
Все, что у них есть, - это имя функции,а одно имя приводит к неоднозначности.Чтобы вызов s t d : : Ьind комп илировался, setAlarm должна быть приведена к корректному типу указателя на функцию:using SetAlarmЗParam'l'ype=void (*) (Ti.me t, Sound s, Duration d) ;11 Теперь все в порядкеauto setSoundВ =s td : : Ьind ( зtatic_cast<SetAlarmЗParam'l'ype> ( setAlarm) ,std: : Ьind ( std: : plus<> ( ) ,std : : Ьiпd ( steady_clock : : now) ,lh) '1,ЗОs ) ;240Гла ва 6. Лямбда-выраженияНо это привносит еще одно различие между лямбда-выражениями и s t d : : Ь i nd.
Внутри оператора вызова функции для s e t SoundL (т.е. оператора вызова функции класса замыкания лямбда-выражения) вызов s e t A l a rm представляет собой обычный вызов функции, который может быть встроен компиляторами:setSoundL ( Sound : : Si ren ) ; // Тело setAlarm может быть встроеноОднако вызов s td : : Ь i n d получает указатель на функцию s e tA l a rm, а это означает,что внутри оператора вызова функции s e tSoundB (т.е.
оператора вызова функции Ьindобъекта) имеет место вызов setAl a rm с помощью указателя на функцию. Компиляторыменее склонны к встраиванию вызовов функций, выполняемых через указатели, а этоозначает, что вызовы s e tAl a rm посредством s e t SoundB будут встроены с куда меньшейвероятностью, чем вызовы посредством s e tSoundL:setSoundВ ( Sound : : Si ren ) ; // Тело setAlarm вряд ли будет встроеноТаким образом, вполне возможно, что применение лямбда-выражений приводит к генерации более быстрого кода, чем применение s t d : : Ьi nd.Пример setAlarm включает только простой вызов функции. Если вы хотите сделатьчто-то более сложное, то перевес в пользу лямбда-выражений только увеличится. Рассмотрим, например, такое лямбда-выражение С++ 1 4, которое выясняет, находится лиаргумент между минимальным ( lowVa l ) и максимальным (highVa l ) значениями, гдеl owVa l и highVal являются локальными переменными.auto betweenL[ lowVa l , highVa l ]( const auto& val ){ return lowVal <= val & & val <= highVa l ; } ;=// С++ 1 4s t d : : Ь i nd может выразить то же самое, но эта конструкция является примером кода,безопасного ТОЛЬКО потому, ЧТО никто не в СОСТОЯНИИ его понять:/ / Как и ранееusing namespace std : : placeholders;auto betweenВ =// С++ 1 4std : : bind ( std : : logical_and<> ( ) ,std : : bind ( std : : less_equal<> ( ) , lowVal , 1 ) ,std: : bind ( std : : less_equal<> ( ) , 1 , highVal ) ) ;В С++ 1 1 мы должны указывать сравниваемые типы, и вызов std : : Ь i nd принимает следующий вид:auto betweenB/ / Версия C++ l lstd : : bind ( std : : logical_and<Ьool > ( ) ,std : : b ind ( std: : less_equal<int> ( ) , lowVal , 1 ) ,std : : bind ( s td : : less_equal<int> ( ) , 1 , highVal ) ) ;Конечно, в С++ 1 1 лямбда-выражение не может получать параметр a u t o, так что здесьтоже надо указывать тип:6.4.
Предпочитайте л ямбда-выражения применению std::Ьind24111 Версия C++ l lauto betweenL[ lowVal , highVal ]=( int val ){ return lowVal < = val & & val < = highVa l ; } ;В любом случае, я надеюсь, все согласятся, что лямбда-версия не только более короткая, но и более понятная и легче поддерживаемая.Ранее я отмечал, что для тех, у кого нет опыта работы с std : : Ьind, заполнители (например, _ 1 , _2 и др.) выглядят магией.