为什么使用带有lambda的std :: bind时产生的对象如此之大?

Nat*_*mal 3 c++ performance lambda c++11

与lambda相比,绑定创建的对象的大小非常大.这让我质疑它的效率.有没有人有任何意见?

我在gcc 4.8.2上使用Linux

    auto f1 = [](int item, int N) { return item < N; };
    auto f2 = bind(f1, _1, 15);

    cout << "\nSize of lambda and bound lambda? "
         << sizeof(f1) << " " << sizeof(f2) << endl; 
Run Code Online (Sandbox Code Playgroud)

这输出:

Size of lambda and bound lambda? 1 8
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 9

有问题的lambda是无状态对象.C++中的无状态对象的大小必须至少为1.

bind只是存储无状态对象的副本,后跟值15.15(an int)的大小是4个字节(在你的系统上),但为了提高效率,它会对齐所有内容.因此1字节的lambda最终占用4个字节.

4 + 4 = 8

从理论上讲,std::bind如果函数对象确实是空的,可以尝试空基本优化来保存存储空间(就像这里的情况一样).但是,由于bind表达式不适用于大容量存储(如数组中),因为它们生成的类型是不透明的,这可能是过度工程化的:bind结果旨在存储在本地(并且除非极端递归,否则会产生额外的几个字节.堆栈无关紧要),或推到类似的东西std::function(免费存储内存跟踪开销使额外的几个字节相对不重要).

但是,更高质量的实现可能会在这里产生一个4字节bind结果.你可能std::tuple只用一小块头痛来酿造你自己的东西,因为bind它相对古怪.

std::tuple使用空基优化来有效地存储空对象.正如你可以在链接看到,含一个空的λ和一个元组的大小int就是4,相同大小的int.

...

OP已经问过为什么int没有进行优化. std::bind大多数(所有?)std库都是用in-language实现的.这意味着它不能获取函数参数并使用它们来更改函数结果的类型.它只能改变运行时结果的状态.

std::bind( whatever, _1, 15 )can 的类型只取决于其参数的类型(及其值类别).所以无论什么类型也可以用来存储std::bind( whatever, _1, 7 ).运行时状态加上类型确定返回对象的逻辑行为.

虽然返回的类型的名称将是实现定义的,但它作为库存在,理论上函数可以基于if语句返回上面的第一个或第二个表达式.(在C++ 11或更高版本中,返回类型推导也可用于命名类型).

所以sizeof需要适应这一点int.然后上面描述了为什么它比一个大int.

这里有两个减轻因素.首先,bind返回的对象实际上不需要存在于优化代码中:编译器可以跟踪15对象并执行as-if转换以消除它.这有时很难做到,特别是如果你搞砸了类似的东西std::function,但这是可能的.

其次,std::bind是一种C++ 03风格的创建函数对象的方法.它很古怪,可能是你应该避免的.Lambdas有一个稍微冗长的语法,但比std::bind这更好地解决问题.

auto f2 = [f1](int y){ return f1(15,y); };
Run Code Online (Sandbox Code Playgroud)

是一个替代你的bind,需要1个字节的空间而不是8个.它的行为略微不同于bind几个方面.首先,它有一个固定的类型参数int而不是bind未固定的参数.其次,它具有不同的类型(该lambda的唯一类型),并且它与其他std::bind从has 返回值的调用没有奇怪的交互std::bind.可能还有其他细微差别.

在C++ 14中,

auto f2 = [f1](auto&& y){ return f1(15,std::forward<decltype(y)>(y)); };
Run Code Online (Sandbox Code Playgroud)

std::bind用非类型参数而不是键入的参数更接近地模仿行为.


对上述所有内容的另一种解释是系统上int有8个字节,并bind使用空基优化.它不太可能.

  • @NathanDoromal:在C++ 14中,您可以使用泛型lambda而不是模板化函数,这些肯定比绑定更好(对于大多数人的口味). (3认同)