当C++lambda表达式有大量引用捕获时,未命名函数对象的大小就会变大
When a C++ lambda expression has a lot of captures by reference, the size of the unnamed function object becomes large
以下代码:
int main() {
int a, b, c, d, e, f, g;
auto func = [&](){cout << a << b << c << d << e << f << g << endl;};
cout << sizeof(func) << endl;
return 0;
}
输出56用g++4.8.2编译
由于所有局部变量都存储在同一堆栈帧中,记住一个指针就足以定位所有局部变量的地址。为什么lambda表达式构造一个如此大的未命名函数对象?
我不明白你为什么看起来很惊讶。
C++标准给出了一组需求,每个实现都可以自由选择任何符合需求的策略。
为什么实现会优化lambda对象的大小?
具体地说,你意识到这将如何将这个lambda的生成代码与周围函数的生成代码联系起来吗?
说起来很容易嘿!这是可以优化的,但要真正优化并确保它在所有边缘情况下都能工作要困难得多。所以,就我个人而言,我更喜欢一个简单有效的实现,而不是一个拙劣的优化尝试…
特别是当工作如此容易时:
struct S { int a, b, c, d, e, f, g; };
int main() {
S s = {};
auto func = [&](){
std::cout << s.a << s.b << s.c << s.d << s.e << s.f << s.g << "n";
};
std::cout << sizeof(func) << "n";
return 0;
}
看马:只有4个字节!
编译器通过堆栈指针引用捕获是合法的。有一个轻微的缺点(因为必须向所述堆栈指针添加偏移)。
在当前包含缺陷的C++标准下,您还必须通过伪指针捕获引用变量,因为绑定的生存期必须与引用的数据一样长,而不是与它直接绑定的引用一样长。
更简单的实现,每个捕获的变量都对应于一个构造函数参数和类成员变量,具有与"更普通"的C++代码一致的显著优势。需要为magic this
做一些工作,但除此之外,lambda闭包是一个带有内联operator()
的bog标准对象实例。对"更正常"的C++代码的优化策略将起作用,错误将主要与"更正常的"代码相同,等等。
如果编译器编写人员使用堆栈框架实现,那么该实现中引用的引用捕获可能无法像在其他编译器中那样工作。当缺陷得到解决(有利于它工作)时,代码将不得不再次更改。本质上,使用更简单实现的编译器几乎肯定会比使用花哨实现的编译器有更少的错误和更多的工作代码。
使用堆栈帧捕获,lambda的所有优化都必须针对该lambda进行自定义。它相当于一个类,该类捕获void*
,对其执行指针运算,并将生成的数据强制转换为类型化指针。这将是非常难以优化的,因为指针算法往往会阻碍优化,尤其是堆栈变量之间的指针算法(通常未定义)。更糟糕的是,这种指针算法意味着堆栈变量状态的优化(消除变量、重叠寿命、寄存器)现在必须以纠缠的方式与lambdas的优化相互作用。
进行这样的优化将是一件好事。另外,由于lambda类型与编译单元绑定,因此干扰lambda的实现不会破坏编译单元之间的二进制兼容性。因此,一旦这些更改被证明是稳定的改进,您就可以相对安全地进行这些更改。然而,如果你真的实现了优化,你真的会希望能够恢复到更简单、经过验证的优化。
我鼓励您为您最喜欢的开源编译器提供补丁,以添加此功能。
因为它就是这样实现的。我不知道这个标准是否说明了它应该如何实现,但我想它的实现定义了在这种情况下lambda对象的大小。
对于编译器来说,存储一个指针并使用偏移量来执行您建议的优化并没有什么错。也许有些编译器会这么做,我不知道。
- 如何创建对象函数指针C++映射?
- 为什么我可以改变常量对象中的成员变量,这是返回常量对象函数的结果?
- 如何将对象函数的实例传递给另一个函数
- 调用模板函数的问题"No matching function for call"参数:迭代器、对象函数
- makefile对我的名称空间对象/函数/构造函数的不确定引用
- 将对象函数转换为函数指针
- 非对象函数/类函数C++
- 线程对象函数 C++
- C 将成员对象函数分配给类成员功能
- 使用基本指针调用派生对象函数
- 可以(通过编译器)使用多少个线程来初始化全局对象(函数main之前)
- C++类对象函数
- 对对象::函数的未定义引用
- 无法弄清楚将多个对象函数作为单独的线程调用的语法
- 在提升作用域出口中调用对象函数
- 使用基指针来使用派生对象函数
- 在for_each lambda 中调用对象函数
- C++:: 模板函数 - 从对象函数获取对象的地址
- Qt5 未解析的外部静态元对象函数
- 通过变量使用对象和对象函数