闭包(lambda 函数)的大小与 std::function 或指针不同

The size of closure (lambda function) not same as std::function or pointer

#include <iostream>
#include <functional>

std::function<int(int)> makeLambda1(int x) {
  return [x] (int y) { return x * y; };
}

auto makeLambda2(int x) {
  return [x] (int y) { return x * y; };
}

auto makeLambda3() {
  return [] (int y) { return 10 * y; };
}

int main() {
  auto lambda1 = makeLambda1(10);
  auto lambda2 = makeLambda2(10);
  auto lambda3 = makeLambda3();
  std::cout << sizeof(lambda1) << " ";
  std::cout << sizeof(lambda2) << " ";
  std::cout << sizeof(lambda3) << " ";
  std::cout << sizeof(int(*)(int)) << std::endl;
}

输出(https://ideone.com/ghoksF):

32 4 1 8
  1. 为什么捕获的 lambda 的大小是 4 个字节而小于 std::function 的大小(32 个字节)?
  2. 为什么不捕获 lambda 的大小是 1 个字节,小于函数指针的大小(8 个字节)?

[1] std::cout << sizeof(lambda1) << " ";

这会打印 std::function 的大小,这是实现定义的。

[2] std::cout << sizeof(lambda2) << " ";

这会打印未命名的唯一 class 类型实例的大小,它只有一个数据成员 - int。这种闭包的大小是 4 - sizeof(int).

[3] std::cout << sizeof(lambda3) << " ";

这会打印未命名的唯一 class 类型实例的大小,它没有数据成员。 emptyclass的大小不能为空,只有1个字节。请参阅 EBO 任何对象或成员子对象的大小(除非 [[no_unique_address]] -- 见下文)(C++20 起)至少为 1即使类型为空 class type.

Lambda 只是重载对象的语法糖 operator()。也就是说

return [x] (int y) { return x * y; };

等同于

class unique_unnamed_type
{
private:
    const int x;
public:
    unique_unnamed_type(int x) : x{x} {}
    auto operator()(int y) { return x * y; } const
};
return unique_unnamed_type{x};

仅仅看一下,您可能会猜到 sizeof(unique_unnamed_type) 应该等于 sizeof(int),因为它的唯一成员是单个 int.


在非捕获的情况下,等效对象只是略有不同。

return [] (int y) { return 10 * y; };

等同于

class unique_unnamed_type
{
private:
    using fp = int(*)(int);
    static auto unnamed_static_function(int y) { return 10 * y; }
public:
    auto operator()(int y) { return unnamed_static_function(y); } const
    operator fp() { return unnamed_static_function; } const
};
return unique_unnamed_type{};

这种类型的大小通常是 1,就像任何没有成员的 class 类型一样(对象需要有一个唯一的地址,所以即使是“空”对象也会占用一个字节的存储空间,除非像空基优化这样的事情。

值得注意的是它比函数指针小,因为它不是函数指针。它只是一个带有隐式转换运算符的 class returns 指向静态成员函数的指针。


std::function 是一个完全不同的野兽。它是 any 具有兼容签名的可调用类型的类型擦除容器。这种类型擦除有一些开销,所以 std::function 通常会比原始函数指针或 lambda 大,但如果 lambda 捕获一个大对象,它可能会更小。 std::function 对象引用的实际可调用对象将动态分配,因此 sizeof(std::function<int(int)>) 不包括实际可调用对象的大小。