С. Мейерс - Эффективный и современный C++ (1114942), страница 57
Текст из файла (страница 57)
Вот основные моменты, которые должны быть понятными.•Невозможно выполнить перемещение объекта в замыкание С++ 1 1, но можно выполнить перемещение объекта в Ьind-объект С++ 1 1 .•Эмуляция захвата перемещением в С++ 1 1 состоит в перемещении объекта в Ьindобъект с последующей передачей перемещенного объекта в лямбда-выражениепо ссылке.•Поскольку время жизни Ьind-объекта совпадает с таковым для замыкания, можнорассматривать объекты в Ьind-объекте так, как будто они находятся в замыкании.В качестве второго примера применения std : : Ь i nd для эмуляции перемещающегозахвата рассмотрим пример кода С++ 1 4, который мы видели ранее и который создаетstd : : unique_ptr в замыкании:auto func[pwstd: : make_unique<Widget> ( ) ] / / Как и ранее,/ / создает pw{ return pw - >i sVa lidated ( )& & pw->isArchived ( ) ; ) ;1 1 в замыкании==А вот как выглядит его эмуляция на С++ 1 1:6.2.Испоnьзуйте инициаnизирующий захват для перемещения объектов в замыкания233auto func=std : : bind ([ ] ( const std: : unique_ptr<Wiclget>& pw){ re turn pw - >isValidated ( )& & pw->isArchived ( ) ; } ,std: : make_unique<Wiclget> ( ));Забавно, что я показываю, как использовать s t d : : Ьind для обхода ограничений лямбда-выражений в С++ 1 1 , поскольку в разделе 6.4 я выступаю как сторонник применениялямбда-выражений вместо s t d : : Ьind.
Однако в данном разделе поясняется, что в С++ 1 1имеются ситуации, когда может пригодиться s t d : : Ь i nd, и это одна из них. (В С++14 такие возможности, как инициализирующий захват и параметры auto, устраняют такиеситуации.)Сnедует запомнить•Для перемещения объектов в замыкания используется инициализирующий захватС++14.•В С++ 1 1 инициализирующий захват эмулируется с помощью написания классоввручную или применения s t d : : Ьi nd.6.3 . Испопьзуйте параметры decl type дпя auto & &дпя передачи с помощью s td : : forwardОдной из самых интересных возможностей С++ 14 являются обобщенные лямбда-выражения - лямбда-выражения, в спецификации параметров которых используется ключевое слово auto.
Реализация этой возможности проста: operator ( ) в классе замыканиялямбда-выражения является шаблоном. Например, для лямбда-выраженияauto f = [ ] ( auto х) { return normal i ze ( х ) ; } ;оператор вызова функции класса замыкания имеет следующий вид:class SomeCompi lerGeneratedClassName {puЬlic :template<typename Т>/ / См . возвращаемый тип autoauto operator ( ) ( Т х) const / / в разделе 1 . 3{ return normal i ze ( x ) ; }1 1 Прочая функциональность11 класса замыкания};В этом примере единственное, что делает лямбда-выражение с параметром х, - это передает его функции norma l i ze. Если пorma l i ze рассматривает значения lvalue не так, какзначения rvalue, это лямбда-выражение написано некорректно, поскольку оно всегдапередает функции norma l i ze lvalнe (параметр х ) , даже если переданный в лямбда-выражение аргумент представляет собой rvalue.234Глава б.
Лямбда-выраженияКорректным способом написания лямбда-выражения является прямая передача хв norma l i ze . Это требует внесения в код двух изменений. Во-первых, х должен быть универсальной ссылкой (см. раздел 5.2), а во-вторых, он должен передаваться в norma l i z eс использованием std : : forward (см.
раздел 5.3). Концептуально это требует тривиальных изменений:auto f[ ] (auto&& х ){ return norma l i ze (std: : forward<???> (x) ) ; 1 :=Однако между концепцией и реализацией стоит вопрос о том, какой тип передаватьв std : : forward, т.е. вопрос определения того, что должно находиться там, где я написал" ? ? ?':Обычно, применяя прямую передачу, вы находитесь в шаблонной функции, принимающей параметр типа т, так что вам надо просто написать s t d : : forward<T>.
В обобщенном лямбда-выражении такой параметр типа т вам недоступен. Имеется т в шаблонизированном операторе operator ( ) в классе замыкания, сгенерированном лямбда-выражением, но сослаться на него из лямбда-выражения невозможно, так что это никак непомогает.В разделе 5.6. поясняется, что если lvalue-apryмeнт передается параметру, являющемуся универсальной ссылкой, то типом этого параметра становится lvalue-ccылкa. Еслиже передается rvalue, параметр становится rvаluе-ссылкой. Это означает, что вне лямбдавыражения мы можем определить, является ли переданный аргумент lvalue или rvalue,рассматривая тип параметра х.
Ключевое слово decl t ype дает нам возможность сделать это (см. раздел 1 .3). Если было передано lvalue, dec l t ype ( х ) даст тип, являющийсяlvаluе-ссылкой. Если же было передано rvalue, decl t уре ( х ) даст тип, являющийся rvаluессылкой.В разделе 5.6 поясняется также, что при вызове std : : fo rward соглашения требуют,чтобы для указания lvalue аргументом типа была lvalue-ccылкa, а для указания rvalue тип, не являющийся ссылкой. В нашем лямбда-выражении, если х привязан к lvalue,declt уре ( х ) даст lvalue-ccылкy. Это отвечает соглашению.
Однако, если х привязанк rvalue, d e c l t уре ( х ) вместо типа, не являющегося ссылкой, даст rvalue-ccылкy. Новзглянем на пример реализации std : : forward в С++ 14 из раздела 5.6:/ / В пространствеtemplate<typename Т>Т&& forward ( remove_reference_t<T>& param) // имен stdreturn static_cast<T&&> ( param) ;Если клиентский код хочет выполнить прямую передачу rvalue типа Widget, он обычно инстанцирует st d : : forward типом W idget (т.е. типом, не являющимся ссылочным),и шаблон std : : forward дает следующую функцию:Widget& & forward (Widget& param){б.3.11 Инстанцирование для1 1 std: : forward, когдаИспопьзуйте п араметры decltype дл я auto&& для передачи с помощью std::forward235return static_ca st<Widqet& & > (param) ; // Т является WidgetНо рассмотрим, что произойдет, если код клиента намерен выполнить прямую передачу того же самого rvalue типа Widget, но вместо следования соглашению об определении Т как не ссылочного типа определит его как rvа\uе-ссылку, т.е.
рассмотрим, что случится, если Т определен как W idge t & & . После начального инстанцирования std : : forwa rdи применения std : : remove_re fe rence_ t, но до свертывания ссылок (еще раз обратитеськ разделу 5.6) std : : forward будет выглядеть следующим образом:Widqet&& & & forward (Widqet& param){return static_cast<Widqet&& &&> ( param) ;/ / (до11 Инстанцирование11 std : : forward nри// Т, равном Widget & &сворачивания ссьuюк)Применение правила сворачивания ссылок о том, что rvа\uе-ссылка на rvа\uе-ссылку становится одинарной rvа\uе-ссылкой, приводит к следующему инстанцированию:Widge t & & forward (Widget& param){return static_cast<Widge t & & > (param) ;1 1 ( после11 Инстанцирование11 std : : forward при/ / Т , равном Widget&&сворачивания ссьmок )Если вы сравните это инстанцирование с инстанцированием, получающимся в результате вызова s t d : : forward с т, равным Widget, то вы увидите, что они идентичны.
Это означает, что инстанцирование s t d : : forward типом, представляющим собой rvа\uе-ссылку,дает тот же результат, что и инстанцирование типом, не являющимся ссылочным.Это чудесная новость, так как dec l t ype ( х ) дает rvа\uе-ссылку, когда в качестве аргумента для параметра х нашего лямбда-выражения передается rvalue. Выше мы установили, что, когда в наше лямбда-выражение передается lvalue, dec lt уре ( х ) дает соответствующи й соглашению тип для передачи в s t d : : forwa rd, и теперь мы понимаем,что для rvalue dec l t ype ( х ) дает тип для передачи st d : : fo rwa rd, который не соответствует соглашению, но тем не менее приводит к тому же результату, что и тип, соответствующий соглашению. Так что как для lvalue, так и для rvalue передача declt ype ( х )в s t d : : forward дает н ам желаемый результат.
Следовательно, наше лямбда-выражениес прямой передачей может быть записано следующим образом:auto f[ ] (auto&& х ){ return norma l i ze (std : : forward<decltype (x) > ( x ) ) ; ) ;=Чтобы это лямбда-выражение принимало любое количество параметров, нам, по сути,надо только шесть точек, поскольку лямбда-выражения в С++ 14 могут быть с переменным ч ислом аргументов:auto f[ ] ( auto&& .
. . x s ){ return normal i ze ( std : : forward<decltype ( xs ) > ( xs ) . . . ) ; ) ;=236Глава б. Лямбда-выраженияСл едует запомнить•Используйте для параметров а u t о & & при их прямой передаче с помощьюs t d : : forward ключевое слово decl t ype.6.4. Предпочитайте nямбда-выраженияприменению s td : : Ьindstd : : Ьi пd в C++ l l является преемником s td : : Ьiпdlst и s t d : : Ьiпd2пd из С++98,но неформально этот шаблон является частью стандартной библиотеки еще с 2005 года.Именно тогда Комитет по стандартизации принял документ, известный как TRl, которыйвключал спецификацию Ь i пd. (В TRl Ь i пd находился в отдельном пространстве имен,так что обращаться к нему надо было как к s t d : : t r 1 : : Ьi пd, а не к s t d : : Ьi пd, а крометого, некоторые детали его интерфейса были иными.) Эта история означает, что некоторые программисты имеют за плечами десятилетний опыт использования s t d : : Ь i пd.Если вы один из них, вы можете не быть склонными отказываться от столь долго веройи правдой служившего вам инструмента.
Это можно понять, и все же в данном случаелучше его сменить, поскольку лямбда-выражения С++ 1 1 почти всегда являются лучшимвыбором, чем std : : Ь iпd. Что касается С++ 14, то здесь лямбда-выражения являются настоящим кладом.В этом разделе предполагается, что вы хорошо знакомы с std : : Ьiпd. Если это не так,вы, вероятно, захотите получить базовые знания о нем, прежде чем продолжить чтение.Что ж, это похвально, тем более что никогда не знаешь, не встретишься ли с применением std : : Ьiпd в коде, который придется читать или поддерживать.Как и в разделе 6.2, я называю функциональные объекты, возвращаемые std : : Ьi пd,Ьiпd-объектами.Наиболее важная причина, по которой следует предпочитать лямбда-выражения, заключается в их большей удобочитаемости.