С. Мейерс - Эффективный и современный C++ (1114942), страница 56
Текст из файла (страница 56)
Он добавил новый механизм, который настолько гибкий, что захват путем перемещения является всего лишь одним из вариантов егоработы. Новая возможность называется инициализирующим захватом (init capture). Онможет делать почти все, что могут делать захваты в С++ 1 1, и еще многое. Единственное,что нельзя выразить с помощью инициализирующего захвата (и от чего, как поясняется6.2. Используйте инициализирующий захват для перемещения объектов в замыкания229в разделе 6.
1 , вам надо держаться подальше), - это режим захвата по умолчанию. (Дляситуаций, охватываемых захватами С++ 1 1 , инициализирующий захват несколько многословнее, так что там, где справляется захват С++ 1 1 , совершенно разумно использоватьименно его.)Применение инициализирующего захвата делает возможным указать1.имя члена-данныхв классе замыкания, сгенерированном из лямбда-выражения, и2.выражение инициализацииэтого члена-данных.Вот как можно использовать и н ициализирующий захват для перемещенияstd : : un i que_ptr в замыкание:1 1 Некоторый полезный типclass WidgetpuЫ i c :bool i sValidated ( ) const;bool isProcessed ( ) cons t ;bool isArchived ( ) con s t ;priva te :};auto pws td : : make_unique<Widget> ( ) ;=11111111auto fuпc[pwstd: : move (pw) ] 1 1{ return pw- >isValidated ( )11& & pw- >isArchived ( ) ; } ; 1 1==Создание Widge t ;std : : make_uniqueсм .
в разделе 4 . 4Настройка •pwИнициализация членав замыкании с помощьюs t d : : move ( pw)Выделенный текст представляет собой инициализирующий захват. Слева от знакаравенства = находится имя члена-данных в классе замыкания, который вы определяете, а справа - инициализирующее выражение. Интересно, что область видимости слеваот "=" отличается от области видимости справа. Область видимости слева - это областьвидимости класса замыкания. Область видимости справа - та же, что и определяемоголямбда-выражения. В приведенном выше примере имя pw слева от = ссылается на члены-данные в классе замыкания, в то время как имя pw справа ссылается на объект, объявленный выше лямбда-выражения, т.е.
на переменную, инициализированную вызовомstd : : ma ke_u п i que. Так что "pws t d : : move ( pw ) " означает "создать член-данные pwв замыкании и инициализировать этот член-данные результатом применения std : : moveк локальной переменной pw':Как обычно, код в теле лямбда-выражения находится в области видимости класса замыкания, так что использованные в нем pw относятся к члену-данным класса замыкания.Комментарий "настройка * pw" в этом примере указывает, что после создания W i dget с помощью s t d : : ma ke_u n i que и до того, как интеллектуальный указатель s t d : : u n i que_pt r на этот W i d g e t будет захвачен лямбда-выражением, W i dget=230Глава 6.
Лямбда-выражениянекоторым образом модифицируется. Если такая настройка не нужна, т.е. если объектWi dget , созданный с помощью s t d : : ma ke_unique, находится в состоянии, приrодномдля захвата лямбда-выражением, локальная переменная pw не нужна, поскольку членданные класса замыкания может быть непосредственно инициализирован с помощьюstd : : make_unique:auto func = [pw = std.: : make_unique<Widget> ( ) ] / / Инициализация{ return pw- >isValidated ( ) / / члена -данных в замыкании&& pw- >i sArchived ( ) ; } ;/ / результатом вызова make_uniqueИз этоrо должно быть ясно, что понятие захвата в С++ 14 значительно обобщенопо сравнению с С++ 1 1 , поскольку в С++ 1 1 невозможно захватить результат выражения.Поэтому еще одним названием инициализирующеrо захвата является обобщенный захват лямбда-выражения (generalized lambda capture).Но что если один или несколько используемых вами компиляторов не поддерживаютинициализирующий захват С++ 14? Как выполнить перемещающий захват в языке, в котором нет поддержки перемещающеrо захвата?Вспомните, что лямбда-выражение - это просто способ rенерации класса и созданияобъекта этоrо типа.
Нет ничеrо, что можно сделать с лямбда-выражением и чеrо нельзябыло бы сделать вручную. Например, код на С++ 1 4, который мы только что рассмотрели,может быть записан на С++ 1 1 следующим образом:class I sValAndArchpuЫic :using DataTypestd : : unique_ptr<Widget > ;explicit IsValAndArch ( DataType & &ptr) / / Применение std : :move/ / описано в разделе 5 . 3: pw ( std : : move (ptr ) ) { }bool operator ( ) ( ) const{ return pw->isValidated ( ) && pw- >isArchived ( ) ; }private :DataType pw ;};auto func=IsVa lAndArch ( s td : : make_unique<Widget> ( ) ) ;Это требует больше работы, чем написание лямбда-выражения, но это не меняет тоrофакта, что если вам нужен класс C++ l l, поддерживающий перемещающую инициализацию своих членов-данных, то ваше желание отделено от вас лишь некоторым временемза клавиатурой.Если вы хотите придерживаться лямбда-выражений (с учетом их удобства это, вероятно, так и есть), то перемещающий захват можно эмулировать в С++ 1 1 с помощью1.перемещения захватываемого объекта в функциональный объект с помощьюstd : : Ьind2.ипередачи лямбда-выражению ссылки на захватываемый объект.6.2.Используйте инициа л изирующий захват для перемещения объектов в замыкания231Если вы знакомы с s t d : : b iпd, код достаточно прост.
Если нет, вам придется немногопривыкнуть к нему, но игра стоит свеч.Предположим, вы хотите создать локальный std : : vector, разместить в нем соответствующее множество значений, а затем переместить его в замыкание. В С++ 14 это просто:1 1 Объект , перемещаемый1 1 в замыкание11 Наполнение данными[ data = std: : move (data) ] / / Инициализирующий захват{ /* Использование данных */ ) ;std: : vector<douЬle> data;auto fuпcЯ выделил ключевые части этого кода: тип объекта, который вы хотите перемещать( s t d : : vector<douЫe>), имя этого объекта (data) и выражение инициализации для инициализирующего захвата ( s t d : : rnove { d a t a ) ).
Далее следует эквивалент этого кодана С++ 1 1 , в котором я выделил те же ключевые части:std : : vector<douЫe> da ta ;// Как и ранее11 Как и ранееauto funcstd : : bind (/ / Эмуляция в С++ 1 1[ ] ( const std : : vector<douЬle>& data) / / инициализирующего11 захвата{ / * Использование данных * / ) ,=std : : move (data));Подобно лямбда-выражениям, std : : Ьiпd создает функциональные объекты. Я называю функциональные объекты, возвращаемые std : : Ьiпd, Ьiпd-объектами. Первый аргумент st d : : Ь i nd - вызываемый объект. Последующие аргументы представляют передаваемые этому объекту значения.Вind-объект содержит копии всех аргументов, переданных s t d : : Ьind.
Для каждогоlvаluе-аргумента соответствующий объект в Ьind-объекте создается копированием. Длякаждого rvalue он создается перемещением. В данном примере второй аргумент представляет собой rvalue (как результат применения std : : rnove; см. раздел 5. 1 ), так что dat aперемещается в Ьiпd-объект. Это перемещающее создание является сутью эмуляции перемещающего захвата, поскольку перемещение rvalue в Ьiпd-объект и есть обходной путьдля перемещения rvalue в замыкание С++ 1 1 .Когда Ьind-объект "вызывается" (т.е. выполняется его оператор вызова функции), сохраненные им аргументы, первоначально переданные в s t d : : Ьi пd, передаются в вызываемый объект.
В данном примере это означает, что когда вызывается Ьind-объект fuпc,лямбда-выражению, переданному в std : : Ьiпd, в качестве аргумента передается созданная в fuпc перемещением копия data.Это лямбда-выражение то же самое, что и использованное нами в C++ l4, за исключением добавленного параметра data. Этот параметр представляет собой lvalue-ccылкyна копию data в Ьind-объекте.
(Это не rvalue-ccылкa, поскольку, хотя выражение, использованное для инициализации копии data (" std : : rnove ( da t a ) "), является rvalue, самапо себе копия dat a представляет собой lvalue.) Таким образом, применение data внутри232Гnава 6. Лямбда-выражениялямбда-выражения будет работать с копией data внутри Ьiпd-объекта, созданной перемещением.По умолчанию функция-член operator ( ) в классе замыкания, сгенерированном излямбда-выражения, является const. Это приводит к тому, что все члены-данные в замыкании в теле лямбда-выражения являются константными. Однако созданная перемещением копия data внутри Ьiпd-объекта не является константной, так что, чтобы предотвратить модификацию этой копии data внутри лямбда-выражения, параметр лямбда-выражения объявляется как указатель на const.
Если лямбда-выражение было объявленокак mutaЫ е, operator ( ) в его классе замыкания не будет объявлен как const, так чтоцелесообразно опустить const в объявлении параметра лямбда-выражения:auto func1 1 Эмуляция в C++ l lstd : : bind ([ ] (std: : vector<douЬle>& dat a ) mutaЫe 1 1 инициализирующего1 1 захвата для лямбда{ /* uses of data * / f ,/ / выражения, объявstd : : move (da t a )// ленного mutaЫe);=Поскольку Ьind-объект хранит копии всех аргументов, переданных s t d : : Ьi nd, Ьindобъект в нашем примере содержит копию замыкания, произведенного из лямбда-выражения, являющегося первым аргументом этого объекта.
Следовательно, время жизни замыкания совпадает со временем жизни Ьind-объекта. Это важно, поскольку это означает,что, пока существует замыкание, существует и Ьind-объект, содержащий объект, захваченный псевдоперемещением.Если вы впервые столкнулись с std : : Ь i nd, вам может понадобиться учебник илисправочник по C++l I, чтобы все детали этого обсуждения встали на свои места в вашейголове.