有问题的 lambda 实际上没有状态。
检查:
struct lambda {
auto operator()() const { return 17; }
};
如果我们有lambda f;,它就是一个空类。以上lambda 不仅在功能上与您的 lambda 相似,而且(基本上)是您的 lambda 的实现方式! (它还需要隐式转换为函数指针运算符,名称lambda 将替换为一些编译器生成的伪guid)
在 C++ 中,对象不是指针。它们是真实的东西。它们只占用存储数据所需的空间。指向对象的指针可以大于对象。
虽然您可能将 lambda 视为指向函数的指针,但事实并非如此。您不能将 auto f = [](){ return 17; }; 重新分配给不同的函数或 lambda!
auto f = [](){ return 17; };
f = [](){ return -42; };
以上是非法的。 f 中没有空间存储要调用的 which 函数——该信息存储在 f 的 type 中,而不是f!
如果你这样做了:
int(*f)() = [](){ return 17; };
或者这个:
std::function<int()> f = [](){ return 17; };
您不再直接存储 lambda。在这两种情况下,f = [](){ return -42; } 都是合法的——所以在这些情况下,我们将 which 函数存储在 f 的值中。并且sizeof(f) 不再是1,而是sizeof(int(*)()) 或更大(基本上,如您所料,指针大小或更大。std::function 具有标准暗示的最小大小(它们必须能够存储“在它们自己内部”可调用的大小达到一定大小)在实践中至少与函数指针一样大)。
在int(*f)() 的情况下,您正在存储一个函数指针,该函数指针的行为就像您调用了该 lambda 一样。这仅适用于无状态 lambda(具有空 [] 捕获列表的 lambda)。
在std::function<int()> f 的情况下,您正在创建一个类型擦除类std::function<int()> 实例,该实例(在这种情况下)使用placement new 将size-1 lambda 的副本存储在内部缓冲区中(并且,如果传入更大的 lambda(具有更多状态),将使用堆分配)。
作为猜测,您认为可能会发生这样的事情。 lambda 是一个对象,其类型由其签名描述。在 C++ 中,决定对手动函数对象实现进行 lambdas零成本 抽象。这使您可以将 lambda 传递给 std 算法(或类似算法),并在编译器实例化算法模板时使其内容对编译器完全可见。如果 lambda 具有像 std::function<void(int)> 这样的类型,则其内容将不会完全可见,而手工制作的函数对象可能会更快。
C++ 标准化的目标是高级编程,与手工编写的 C 代码相比零开销。
既然您了解您的f 实际上是无状态的,那么您的脑海中应该还有另一个问题:lambda 没有状态。为什么大小没有0?
有一个简短的答案。
C++ 中的所有对象在标准下的最小尺寸必须为 1,并且相同类型的两个对象不能具有相同的地址。它们是相连的,因为 T 类型的数组会将元素放在 sizeof(T) 之间。
现在,由于它没有状态,所以有时它不会占用任何空间。这在它“单独”时不会发生,但在某些情况下它可能会发生。 std::tuple 和类似的库代码利用了这一事实。以下是它的工作原理:
由于 lambda 等效于具有 operator() 重载的类,因此无状态 lambda(具有 [] 捕获列表)都是空类。他们有sizeof 和1。事实上,如果你从它们继承(这是允许的!),它们将不会占用空间只要它不会导致同类型地址冲突。 (这称为空基优化)。
template<class T>
struct toy:T {
toy(toy const&)=default;
toy(toy &&)=default;
toy(T const&t):T(t) {}
toy(T &&t):T(std::move(t)) {}
int state = 0;
};
template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }
sizeof(make_toy( []{std::cout << "hello world!\n"; } )) 是 sizeof(int)(好吧,上面的内容是非法的,因为您不能在未评估的上下文中创建 lambda:您必须创建一个名为 auto toy = make_toy(blah); 然后执行 sizeof(blah),但这只是噪音)。 sizeof([]{std::cout << "hello world!\n"; }) 仍然是 1(类似资格)。
如果我们创建另一个玩具类型:
template<class T>
struct toy2:T {
toy2(toy2 const&)=default;
toy2(T const&t):T(t), t2(t) {}
T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }
这有 两个 lambda 副本。因为他们不能共享同一个地址,sizeof(toy2(some_lambda)) 是2!