为什么一个lambda具有1个字节的大小?

我正在使用C ++中的一些lambdas的内存,但是我对它们的大小感到有点困惑。

这是我的testing代码:

#include <iostream> #include <string> int main() { auto f = [](){ return 17; }; std::cout << f() << std::endl; std::cout << &f << std::endl; std::cout << sizeof(f) << std::endl; } 

你可以在这里运行它: http : //fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598

ouptut是:

 17 0x7d90ba8f626f 1 

这表明我的lambda的大小是1。

  • 这怎么可能?

  • lambda是不是应该至less指向它的实现?

有问题的lambda实际上没有状态

检查:

 struct lambda { auto operator()() const { return 17; } }; 

如果我们有lambda f; ,这是一个空的class级。 不仅上面的lambdafunction上与你的lambda类似,而且(基本上)你的lambda是如何实现的! (它也需要隐式强制转换为函数指针运算符,名称lambda将被replace为一些由编译器生成的伪guid)

在C ++中,对象不是指针。 他们是实际的东西。 它们只占用将数据存储在其中的空间。 指向对象的指针可以大于对象。

虽然你可能会认为lambda是指向函数的指针,但它不是。 你不能重新分配auto f = [](){ return 17; }; auto f = [](){ return 17; }; 到不同的function或lambda!

  auto f = [](){ return 17; }; f = [](){ return -42; }; 

以上是非法的f没有空间来存储哪个函数将被调用 – 该信息存储在ftypes中,而不是f的值!

如果你这样做:

 int(*f)() = [](){ return 17; }; 

或这个:

 std::function<int()> f = [](){ return 17; }; 

你不再直接存储lambda。 在这两种情况下, f = [](){ return -42; } f = [](){ return -42; }是合法的 – 所以在这些情况下,我们正在存储哪个函数调用f的值。 和sizeof(f)不再是1 ,而是sizeof(int(*)())或更大(基本上,是指针大小或更大,正如你所期望的那样) std::function具有标准暗示的最小尺寸必须能够存储“自己内部”的可调参数达到一定的大小),这在实际中至less与函数指针一样大)。

int(*f)()情况下,你正在存储一个函数指针,该函数的行为就像 – 如果你调用了lambda。 这只适用于无状态的lambda(具有空的[]捕获列表的)。

std::function<int()> f情况下,你正在创build一个type-erasure类std::function<int()>实例,在这个例子中,使用placement new来存储size-1在一个内部缓冲区中的lambda(和,如果一个更大的lambda传入(具有更多的状态),将使用堆分配)。

作为一个猜测,像这样的东西可能是你想的事情。 lambda是一个对象,其types由其签名来描述。 在C ++中,决定通过手动函数对象实现来实现lambdas 零成本抽象。 这可以让你将一个lambda传递给一个stdalgorithm(或类似的),并且当它实例化algorithm模板时,它的内容对于编译器是完全可见的。 如果lambda具有像std::function<void(int)> ,则其内容将不会完全可见,而且手工制作的函数对象可能会更快。

C ++标准化的目标是高级编程,对手工编写的C代码没有任何开销。

现在你明白你的f实际上是无状态的,你的脑海里应该有另一个问题:lambda没有状态。 为什么不是大小有0


有简短的答案。

C ++中的所有对象在标准下的最小大小必须为1,并且相同types的两个对象不能有相同的地址。 这些是连接的,因为typesT的数组将具有放置sizeof(T)的元素分开。

现在,因为没有国家,有时可能不占用空间。 当它是“独立的”时,这不可能发生,但在某些情况下它可能发生。 std::tuple和类似的库代码利用这个事实。 下面是它的工作原理:

由于lambda等同于operator()重载的类,所以无状态lambda(带有[]捕获列表)都是空类。 他们有1 sizeof 。 事实上,如果你从它们inheritance(这是允许的!), 只要它不会导致同一types的地址冲突 ,它们将不占用空间。 (这被称为空基优化)。

 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) (上面是非法的,因为您不能在非评估的上下文中创buildlambda:你必须创build一个名为auto toy = make_toy(blah);然后做sizeof(blah) ,但那只是噪音)。 sizeof([]{std::cout << "hello world!\n"; })仍然是1 (类似资格)。

如果我们创造另一种玩具types:

 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

lambda不是函数指针。

lambda是一个类的实例。 您的代码大致相当于:

 class f_lambda { public: auto operator() { return 17; } }; f_lambda f; std::cout << f() << std::endl; std::cout << &f << std::endl; std::cout << sizeof(f) << std::endl; 

代表一个lambda的内部类没有类成员,因此它的sizeof()是1(不能为0,因为其他地方已经充分说明了)。

如果你的lambda捕获了一些variables,它们将相当于类成员,并且你的sizeof()将相应地指示。

你的编译器或多或less地把lambda翻译成下面的structtypes:

 struct _SomeInternalName { int operator()() { return 17; } }; int main() { _SomeInternalName f; std::cout << f() << std::endl; } 

由于该结构没有非静态成员,因此它与空结构的大小相同,即1

只要你添加一个非空的捕获列表到你的lambda:

 int i = 42; auto f = [i]() { return i; }; 

这将转化为

 struct _SomeInternalName { int i; _SomeInternalName(int outer_i) : i(outer_i) {} int operator()() { return i; } }; int main() { int i = 42; _SomeInternalName f(i); std::cout << f() << std::endl; } 

由于生成的结构现在需要存储捕获的非静态int成员,所以它的大小将增长到sizeof(int) 。 捕捉更多东西的大小将继续增长。

(请用一点盐的结构来比喻一下,虽然这是一个很好的方式来推断lambdas是如何在内部工作的,但这不是编译器会做的直译)

lambda不应该在mimumum上指向它的实现吗?

不必要。 根据标准,唯一的未命名类的大小是实现定义的 。 摘自[expr.prim.lambda] ,C ++ 14(强调我的):

lambdaexpression式(也是闭包对象的types)的types是一个唯一的,未命名的非联合类types – 称为闭包types – 其属性在下面描述。

[…]

一个实现可以定义封闭types与下面描述的不同,只要这不改变程序的可观察行为,而不是改变

– 封闭types的大小和/或alignment

– 封闭types是否可以复制(第9条),

– 封闭types是标准布局类(第9章)还是

– 封闭types是否是POD类(第9章)

在你的情况下 – 对于你使用的编译器 – 你得到的大小为1,这并不意味着它是固定的。 它可以在不同的编译器实现之间变化

http://en.cppreference.com/w/cpp/language/lambda

lambdaexpression式构造一个名为闭包types的唯一未命名的非并非非聚合类types的未命名的prvalue临时对象,在最小的块作用域,类作用域或命名空间作用域中声明(用于ADL) lambdaexpression式。

如果lambdaexpression式通过复制 (隐式地使用capture子句[=]或显式地使用不包含字符的捕获(例如[a,b,c]) 捕获任何东西 ), 闭包types包括未命名的非静态数据成员以非特定顺序宣布,持有如此俘获的所有实体的副本。

对于通过引用捕获的实体(使用默认捕获[&]或使用字符&时,例如[&a,&b,&c]), 如果在封闭types中声明其他数据成员则不指定

http://en.cppreference.com/w/cpp/language/sizeof

应用于空类types时,始终返回1。