Подтвердить что ты не робот

Когда 'this' захватывается в лямбда?

У меня есть функция в классе, которая определяет лямбда и сохраняет ее в локальной статической переменной:

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)

Я планирую напечатать следующее:

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

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

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

(одинаковый адрес объекта также печатается с каждым)

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

Вывод:

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

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

Ответ 3

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

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

  • Создайте лямбду, захватив & A, как это, что вызывает A.print();
  • Назначьте лямбда print_func
  • Вызовите эту лямбду (через print_func)
  • Назовите эту лямбду еще раз.

Захваченные значения всегда фиксируются при создании лямбда - что в этом случае во время первого вызова 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, который затем используется далее.

Ответ 5

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

Эта переменная создается при первом потоке кода, используя любые переменные, доступные в то время.

Пример:

#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;

Ответ 6

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