我在一个类中有一个函数,它定义一个lambda并将其存储在局部静态变量中:
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();
}
(现场样本)
我期望打印的是:
A::print()
B::print()
但我真正得到的是:
A::print()
A::print()
(每个对象地址也打印相同)
我怀疑这是由于this
捕获。我假设它在调用时会捕获this
的值,但是它似乎在定义 lambda 的那一刻被捕获。
有人可以解释 lambda 捕获的语义吗?它们何时实际提供给函数?所有捕获类型都相同,还是this
特殊情况?删除static
可以解决问题,但是在我的生产代码中,我实际上是将lambda存储在一个稍重的对象中,该对象表示我稍后插入信号的插槽。
这与lambda捕获的语义无关。这就是static
的工作方式。
static
函数范围的变量只初始化一次。整个程序中只有一个这样的对象。它将在第一次调用函数时初始化(更具体地说,第一次执行static
语句时)。因此,用于初始化 static
变量的表达式只调用一次。
因此,如果一个static
函数范围的变量使用基于函数参数之一(如this
)的数据初始化,那么它只会从该函数的第一次调用中获取参数。
您的代码创建单个 lambda。它不会在每次调用函数时创建不同的 lambda。
您似乎想要的行为不是函数局部static
变量,而是对象成员。因此,只需将一个std::function
对象放在类本身中,如果它为空,则必须call_print
初始化它。
lambda 在第一次调用封闭函数 A::call_print()
时实例化。从第一次在A
对象上调用它以来,将捕获该对象的this
。如果颠倒调用顺序,则会看到不同的结果:
b.call_print();
a.call_print();
输出:
B::print()
B::print()
它更多地与函数本地静态对象的初始化语义有关,而不是 lambda 捕获的语义。
静态局部变量在第一次执行其声明时初始化。
在这种情况下,您:
- 创建一个 lambda,捕获 &A,调用 A.print();
- 将该 lambda 分配给
print_func
- 调用该 lambda(通过
print_func
) - 再次调用该 lambda。
捕获的值始终在创建 lambda 时捕获 - 在本例中是在第一次调用 call_print
期间。
我认为捕获不是这里的问题,而是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";
}
};
如果没有 lambda,这几乎是一样的。
如果您查看代码,很明显您的调用a.call_print();
使用指向对象的指针初始化ptr
a
然后进一步使用。
这个问题与其说是关于lambda的行为,不如说是关于函数中静态变量的行为。
变量是在代码第一次流过它时使用当时可用的任何变量创建的。
例:
#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;
实际上,捕获的值是在定义 lambda 时设置的,而不是在调用时设置的。因为您要为定义 lambda 的表达式设置静态变量,所以这只会在第一次调用函数 call_print
时发生(通过管理静态变量的规则)。因此,此后的所有call_print
调用实际上都会获得相同的 lambda,即其this
设置为 &a
的 lambda。