为什么一个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级。 不仅上面的lambda
function上与你的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
没有空间来存储哪个函数将被调用 – 该信息存储在f
的types中,而不是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传递给一个std
algorithm(或类似的),并且当它实例化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。