所讨论的 lambda 实际上有no state.
Examine:
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; };
以上是illegal。没有房间了f
储藏which函数将被调用——该信息存储在type of f
,不在值中f
!
如果你这样做:
int(*f)() = [](){ return 17; };
or this:
std::function<int()> f = [](){ return 17; };
您不再直接存储 lambda。在这两种情况下,f = [](){ return -42; }
是合法的——所以在这些情况下,我们正在存储which我们正在调用的函数的值是f
. And sizeof(f)
不再是1
, 反而sizeof(int(*)())
或更大(基本上,如您所期望的,是指针大小或更大。std::function
具有标准隐含的最小大小(它们必须能够在“内部”存储达到一定大小的可调用对象),实际上至少与函数指针一样大)。
In the int(*f)()
在这种情况下,您正在存储一个指向函数的函数指针,该函数的行为就像您调用该 lambda 一样。这仅适用于无状态 lambda(带有空的 lambda)[]
捕获列表)。
In the std::function<int()> f
情况下,您正在创建一个类型擦除类std::function<int()>
实例(在本例中)使用placement new 将 size-1 lambda 的副本存储在内部缓冲区中(并且,如果传入更大的 lambda(具有更多状态),将使用堆分配)。
作为猜测,您可能认为正在发生类似的事情。 lambda 是一个对象,其类型由其签名描述。在 C++ 中,决定使用 lambda零成本对手动函数对象实现的抽象。这可以让你将 lambda 传递到std
算法(或类似的),并在实例化算法模板时使其内容对编译器完全可见。如果 lambda 有这样的类型std::function<void(int)>
,它的内容不会完全可见,并且手工制作的函数对象可能会更快。
C++ 标准化的目标是对手工编写的 C 代码进行零开销的高级编程。
现在你明白了你的f
事实上是无状态的,你脑子里应该还有另一个问题:lambda 没有状态。为什么没有尺寸0
?
有一个简短的答案。
标准下C++中的所有对象的最小大小必须为1,并且同一类型的两个对象不能具有相同的地址。这些是相连的,因为类型数组T
将放置元素sizeof(T)
apart.
现在,由于它没有状态,有时它可以不占用任何空间。当它“单独”时这不会发生,但在某些情况下它可能会发生。std::tuple
类似的库代码利用了这一事实。下面是它的工作原理:
因为 lambda 相当于一个类operator()
重载的、无状态的 lambda(带有[]
捕获列表)都是空类。他们有sizeof
of 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}; }
the sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
is 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))
is 2
!