2 Мотивации для лямбда-захвата * это значение
Действительно захватывая * это значение позволяет копировать неявно объявленное замыкание перед вызовом функции замыкания.
2.1 Асинхронная отправка лямбды
Асинхронная отправка замыканий является краеугольным камнем параллелизма и параллелизма.
Когда лямбда-выражение отправляется асинхронно из нестатической функции-члена через std :: async или другой механизм диспетчеризации параллелизма / параллелизма, включающий * этот класс не может быть захвачен по значению. Таким образом, когда будущее (или другой дескриптор) отправленной лямбды переживает исходный класс, лямбда-захваченный этот указатель недействителен.
class Work {
private:
int value ;
public:
Work() : value(42) {}
std::future<int> spawn()
{ return std::async( [=]()->int{ return value ; }); }
};
std::future<int> foo()
{
Work tmp ;
return tmp.spawn();
// The closure associated with the returned future
// has an implicit this pointer that is invalid.
}
int main()
{
std::future<int> f = foo();
f.wait();
// The following fails due to the
// originating class having been destroyed
assert( 42 == f.get() );
return 0 ;
}
2.2 Отправка асинхронных замыканий на данные
Современные и будущие аппаратные архитектуры, специально ориентированные на параллелизм и параллелизм, имеют гетерогенные системы памяти. Например, области NUMA, присоединенная память ускорителя и стеки обработки в памяти (PIM). В этих архитектурах это часто приводит к значительному улучшению производительности, если замыкание копируется в данные, с которыми оно работает, в отличие от перемещения данных в замыкание и из него.
Например, параллельное выполнение замыкания в больших областях данных, охватывающих области NUMA, будет более производительным, если копия этих замыканий, находящихся в той же области NUMA, воздействует на эти данные. Если значение true (автономное) лямбда-замыкание захвата по значению было дано для параллельной диспетчеризации, например, в технической спецификации параллелизма, то библиотека может создавать копии этого замыкания в каждой области NUMA, чтобы улучшить локальность данных для параллельной вычисление. В другом примере замыкание, отправленное на подключенный ускоритель с отдельной памятью, должно быть скопировано в память ускорителя, прежде чем может произойти выполнение. Таким образом, текущая и будущая архитектура требуют возможности копирования замыканий в данные.
2.3 Обременительный и подверженный ошибкам обходной путь
Потенциальным обходным решением для этого недостатка является явное получение копии исходного класса.
class Work {
private:
int value ;
public:
Work() : value(42) {}
std::future<int> spawn()
{
return std::async( [=,tmp=*this]()->int{ return tmp.value ; });
}
};
Этот обходной путь имеет два обязательства. Во-первых, указатель this также фиксируется, что дает значительную возможность ошибочно ссылаться на элемент this-> вместо tmp. член. Во-вторых, это является обременительным и контрпродуктивным для введения асинхронно распределенных лямбда-выражений в существующий код. Рассмотрим случай замены цикла for в нестатической функции-члене параллелью для каждой конструкции, как в технической спецификации параллелизма.
class Work {
public:
void do_something() const {
// for ( int i = 0 ; i < N ; ++i )
foreach( Parallel , 0 , N , [=,tmp=*this]( int i )
{
// A modestly long loop body where
// every reference to a member must be modified
// for qualification with 'tmp.'
// Any mistaken omissions will silently fail
// as reference