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


#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::函数的大小(32字节(
  2. 为什么不捕获lambda的大小是1字节,而小于指向函数的指针的大小(8字节(

Lambdas只是具有重载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,就像任何没有成员的类类型一样(对象需要有一个唯一的地址,所以即使是"空"对象也会占用一个字节的存储空间,除非进行空基优化(。

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


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

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

这将打印实现定义的std::function的大小。

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

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

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

这将打印没有数据成员的未命名的唯一类类型实例的大小。空类的大小不能为空,并且只有1个字节。请参阅EBO,任何对象或成员子对象的大小(除非[[no_unique_address]]--见下文((由于C++20(都必须至少为1,即使该类型是空类类型

最新更新