为什么使用 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
表达式不适用于大容量存储(如在数组中),因为它们产生的类型是不透明的,这可能是过度设计的:bind
s 结果旨在存储在本地(并且除非极端递归,堆栈上额外的几个字节无关紧要),或者推入类似 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
使用空基优化。可能性不大。
与 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
表达式不适用于大容量存储(如在数组中),因为它们产生的类型是不透明的,这可能是过度设计的:bind
s 结果旨在存储在本地(并且除非极端递归,堆栈上额外的几个字节无关紧要),或者推入类似 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
使用空基优化。可能性不大。