2016-07-13 2 views
24

У меня есть функция в классе, который определяет лямбда и сохраняет его в локальной статической переменной:Когда это «захвачено» в лямбда?

class A 
{ 
public: 
    void call_print() 
    { 
     static auto const print_func = [this] { 
      print(); 
     }; 

     print_func(); 
    }; 

    virtual void print() 
    { 
     std::cout << "A::print()\n"; 
    } 
}; 

class B : public A 
{ 
public: 
    virtual void print() override 
    { 
     std::cout << "B::print()\n"; 
    } 
}; 

Я также выполнить следующий тест:

int main() 
{ 
    A a; 
    B b; 

    a.call_print(); 
    b.call_print(); 
} 

(Live Sample)

Я ожидаю, что будет напечатан:

A::print() 
B::print() 

Но то, что я действительно получаю:

A::print() 
A::print() 

(Тот же адрес объекта также печатается с каждым)

Я подозреваю, что это связано с this захвата. Я предположил, что он будет захватывать значение , когда оно вызывается, однако оно, кажется, захватывается в момент, когда определяется лямбда.

Может ли кто-нибудь объяснить семантику захвата лямбда? Когда они действительно получают функцию? Это то же самое для всех типов захвата, или this частный случай? Удаление static устраняет проблему, однако в моем производственном коде я фактически храню лямбду в немного более тяжелом объекте, который представляет собой слот, в который я вставляю сигнал позже.

ответ

36

Это не имеет никакого отношения к семантике лямбда-захвата. Это просто то, как работает static.

A static переменная с функциональной областью инициализируется ровно один раз. В вашей программе есть только один такой объект. Он будет инициализирован при первом вызове функции (точнее, при выполнении оператора static). И поэтому выражение, используемое для инициализации переменной static, вызывается только один раз.

Так, если переменная с функциональной областью static инициализируется данными, основанными на одном из параметров функции (например, this), тогда она будет получать параметры только от первого вызова этой функции.

Ваш код создает единый лямбда. Он не создает разные lambdas при каждом вызове функции.

Поведение, которое вы, кажется, хотите, не является функцией-local static переменной, а членом объекта. Поэтому просто поместите объект std::function в сам класс и введите call_print, если он пуст.

+11

Может быть, стоит добавить, что это на самом деле представляет собой очень опасный стиль программирования, так как если 'a' удаляется, будущие вызовы' call_print' будут вызывать неопределенное поведение и, скорее всего, сбой. – Xirema

+0

@Xirema Это очень верно, но я не думаю, что это поведение, которое он пытается получить. –

+0

Я понимаю семантику 'static', что я пытаюсь понять, если лямбда« обещает »захватить' this' * позже *, или если она будет захвачена * справа, то *. Однако вы ответили на это в своем ответе. Я хотел, чтобы контейнер для лямбда был статичным, но не захватом. Я надеялся, что они будут разделены между определением и вызовом. –

2

Действительно, значение захвата задается при определении лямбда, а не при его вызове. Поскольку вы устанавливаете статическую переменную в выражение, определяющее лямбда, это происходит только в первый раз, когда вызывается функция call_print (по правилам, определяющим статические переменные). Таким образом, все call_print называет по сути тот же самый лямбда, тот, у которого this установлен в &a.

12

Лямбда создана в первый раз, когда включена закрывающая функция A::call_print(). С первого раза, когда вы вызываете его на объекте A, объект этого объекта захватывается.Если обратный порядок вызова, вы увидите другой результат:

b.call_print(); 
a.call_print(); 

Выход:

B::print() 
B::print() 

Это больше связано с семантикой инициализации функции локального статических объектов, чем те лямбда-захват.

8

Статические локальные переменные инициализируются при первом выполнении их декларации.

В этом случае:

  1. Создание лямбда, захватывая & А как это, что вызывает A.print();
  2. Назначают, что лямбда на print_func
  3. Зов, что лямбда (через print_func)
  4. вызовов, что лямбда снова.

Захваченные значения всегда захватываются при создании лямбда - в этом случае при первом вызове call_print.

4

Я не думаю, что проблема захвата здесь, но ключевое слово static. Подумайте о вашем коде, как это:

class A 
{ 
public: 
    void call_print() 
    { 
     static A* ptr = this; 

     ptr->print(); 
    }; 

    virtual void print() 
    { 
     std::cout << "A::print()\n"; 
    } 
}; 

class B : public A 
{ 
public: 
    virtual void print() override 
    { 
     std::cout << "B::print()\n"; 
    } 
}; 

Это почти так же, без лямбда.

Если вы посмотрите на код становится совершенно ясно, что ваш звонок a.call_print(); инициализирует ptr с указателем на объект a, который затем используется в дальнейшем.

3

Этот вопрос касается не столько поведения лямбда, сколько поведения статических переменных в функциях.

Переменная создается, когда на нее проходит первый временной код, используя любые переменные, доступные в это время.

пример:

#include <iostream> 

int foo(int v) 
{ 
    static int value = v; 
    return v; 
}; 

int main() 
{ 
    std::cout << foo(10) << std::endl; 
    std::cout << foo(11) << std::endl; 
} 

ожидается:

10 
10 

Потому что это эквивалентно:

foo::value = 10; 
std::cout << foo::value << std::endl; 
// foo::value = 11; (does not happen) 
std::cout << foo::value << std::endl; 
Смежные вопросы