闭包(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
- 为什么捕获的 lambda 的大小是 4 个字节而小于 std::function 的大小(32 个字节)?
- 为什么不捕获 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)>)
不包括实际可调用对象的大小。
#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
- 为什么捕获的 lambda 的大小是 4 个字节而小于 std::function 的大小(32 个字节)?
- 为什么不捕获 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)>)
不包括实际可调用对象的大小。