В C ++ захватывая "* this" в лямбде - PullRequest
0 голосов
/ 07 ноября 2018
struct S { 
    int x = 4;
    void f(int i); 
};

void S::f(int i) {
    [=, *this]{}; // OK: captures this by value. See below.
}

Почему лямбда [=, *this]{}; действительна?

1 Ответ

0 голосов
/ 07 ноября 2018

Не знаю, что вы имеете в виду, но p0018r0 может ответить на ваш вопрос:

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
...