为什么使用 std::bind 和 lambda 时生成的对象这么大?

Why is the object produced when using std::bind with a lambda so large?

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

这输出:

Size of lambda and bound lambda? 1 8

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

bind 只是存储无状态对象的副本,后跟值 15。 sizeof 15(int)是 4 个字节(在您的系统上),但为了提高效率,它对齐所有内容。所以 1 字节的 lambda 最终占用了 4 个字节。

4+4=8

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

但是,是的,更高质量的实现可以在这里实现 4 字节 bind 结果。您可能会使用 std::tuple 来酿造自己的啤酒,而不会头疼,因为 bind 相对古怪。

std::tuple uses the empty base optimization 有效地存储空对象。正如您在 link 中看到的那样,包含空 lambda 和 int 的元组的大小为 4,与 int.[=51= 的大小相同]

...

OP 问为什么 int 没有被优化掉。 std::bind 大多数(全部?)std 库是用语言实现的。这意味着它不能获取函数参数并使用它们来更改函数结果的类型。它只能改变运行次结果的状态。

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

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

所以 sizeof 需要适应那个 int。那么上面描述了为什么比int.

这里有两个缓解因素。首先,bind returns 的对象实际上不需要在优化代码中存在:编译器可以跟踪 15 和对象并进行 as-if 转换以摆脱它。这有时很难做到,尤其是当你乱搞 std::function 之类的东西时,但这是可能的。

其次,std::bind是一种C++03风格的创建函数对象的方式。这很古怪,可能是您应该避免的事情。 Lambda 的语法稍微冗长,但比 std::bind 更好地解决了问题。

auto f2 = [f1](int y){ return f1(15,y); };

bind 的替代方案,它占用 space 的 1 个字节而不是 8 个字节。它的行为在某些方面与 bind 略有不同。首先,它有一个固定类型参数 int 而不是 bind 的不固定参数。其次,它具有不同的类型(该 lambda 的唯一类型),并且它与来自 std::bind 的 return 值所具有的其他 std::bind 调用没有奇怪的交互。可能还有其他细微差别。

在 C++14 中,

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

会更接近地模仿 std::bind 的行为,使用非类型化参数而不是类型化参数。


对上述所有内容的另一种解释是 int 在您的系统上是 8 个字节,而 bind 使用空基优化。可能性不大。