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