所讨论的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
存放这些功能将被称为-信息存储在类型的f
,而不是价值f
!
如果您这样做:
int(*f)() = [](){ return 17; };
或这个:
std::function<int()> f = [](){ return 17; };
您不再直接存储lambda。在这两种情况下,f = [](){ return -42; }
是合法的-所以在这种情况下,我们都存储其功能,我们在价值调用f
。并且sizeof(f)
不再是1
,而是而是sizeof(int(*)())
更大(基本上是指针大小,如您所期望的那样。) std::function
具有标准所隐含的最小大小(它们必须能够将“内部”可调用对象存储为一定大小),该大小实际上至少与函数指针一样大)。
在这种int(*f)()
情况下,您将存储一个函数指针,该函数指针的行为与调用lambda时的行为相同。这仅适用于无状态Lambda([]
捕获列表为空的Lambda)。
在这种std::function<int()> f
情况下,您要创建一个类型擦除类std::function<int()>
实例(在这种情况下),该实例使用placement new将一个大小为1的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)
(当然,以上是非法的,因为你不能建立在非评估方面拉姆达:你必须创建一个名为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
!
struct
带有的aoperator()
)