2014-04-18 13 views
4

以下代码:当C++ lambda表达式有很多通过引用捕获的,无名函数对象的尺寸变大

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 克++ 4.8.2

编译由于所有局部变量都存储在同一个栈帧中,记住一个指针足以定位所有局部变量的地址。为什么lambda表达式构造一个如此大的未命名的函数对象?

+2

它的实现依赖于是否存储单个指向堆栈帧的指针,或者是否存储指向每个捕获对象的指针或引用。就你而言,它似乎是后者。假设你使用的是64位平台,每个指针有7个变量* 8个字节= 56。 – Praetorian

回答

4

我不明白你为什么看起来很惊讶。

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; 
} 

Look Ma: 4 bytes only!

+0

这个问题似乎是基于64位系统的8个字节,因为它使用56个字节来存储7个指针。 – Kleist

+0

@Kleist:是的,最有可能的,但这是依赖于实现/平台。例如,可以想象在调试模式下在lambda中嵌入更多信息,或者在这种情况下能够完全优化引用本身,所以我只想指出它是* slimmer * :) –

+0

当函数复杂时(可能有许多不同的嵌套块),我不认为这个解决方法很简单,并且lambda表达式可能会使用很多不同的局部变量。它需要重新组织整个功能的结构。不过,我仍然接受你的回答。 – user3547691

1

因为这就是它的实现。我不知道该标准是否说明了应该如何实现,但我想它的实现定义了lambda对象在这种情况下有多大。

编译器将存储单个指针并使用偏移量按照您的建议进行优化,这不会有什么错误。也许有些编译器这样做,我不知道。

+0

如果有编译器或计划编译,我会感到非常惊讶。这很容易解决(只需将所有内容打包到一个结构体中),并且通过引入当今独立并且可以自由地重新组织代码的函数(外层和lambda)之间的相互依赖关系来复杂化代码的生成。 –

+0

你可能是对的,对编译器进行优化没有多大意义。 – Kleist

1

编译器通过引用通过堆栈指针捕获是合法的。有一个轻微的缺点(因为偏移量必须被添加到所述堆栈指针)。

在包含缺陷的当前C++标准下,您还必须通过伪指针捕获引用变量,因为绑定的生命周期必须与引用的数据一样持续,而不是直接绑定到的引用。

更简单的实现,其中每个捕获的变量对应于构造函数参数和类成员变量,具有严重的优势,即它与“更普通的”C++代码一致。需要完成一些神奇的工作this,但除此之外,lambda闭包是一个内嵌operator()的bog标准对象实例。 “更普通”C++代码的优化策略将起作用,错误将与“更普通”代码大部分相同,等等。

如果编译器编写者没有使用堆栈帧实现,可能引用了引用捕获在那个实现中将不能像其他编译器那样工作。当缺陷得到解决(有利于工作)时,代码将不得不再次更改。从本质上讲,使用简单实现的编译器几乎可以肯定比那些使用花式实现的编译器更少的错误和更多的工作代码。

随着堆栈帧捕获,lambda的所有优化都必须为该lambda定制。它相当于一个捕获void*的类,对它进行指针运算,并将结果数据转换为类型指针。这将是非常难进行优化,因为指针算术往往阻止优化,尤其是堆栈变量之间的指针算术(通常是未定义的)。更糟糕的是,这样的指针算术意味着堆栈变量状态的优化(消除变量,重叠生命期,寄存器)现在必须与纠缠方式下的lambda优化相互作用。

处理这样的优化是件好事。作为奖励,因为lambda类型绑定到编译单元,所以搞乱lambda实现不会破坏编译单元之间的二进制兼容性。所以你可以相对安全地做这样的改变,一旦它们证明了稳定的改进。但是,如果您确实实施了这种优化,那么您确实会希望能够恢复到更简单的已被证明的优化。

我鼓励您为您最喜爱的开源编译器提供补丁以添加此功能。