如果使用lambda,std::unique_ptr如何没有大小开销

How std::unique_ptr have no size overhead if using lambda

本文关键字:何没 开销 ptr unique lambda 如果 std      更新时间:2023-10-16

继续这个问题,
std::unique_ptr如何没有大小开销?

我做了以下代码,结果出乎意料:

#include <memory>
#include <cstdio>
void f(void *p){
free(p);
}
int main(){
auto x1 = [](void *p){ free(p); };
auto x2 = [](void *p){ f(p); };
printf("%zun", sizeof(std::unique_ptr<int>                 )); //  8, expected
printf("%zun", sizeof(std::unique_ptr<int, decltype(&f)>   )); // 16, expected
printf("%zun", sizeof(std::unique_ptr<int, decltype(x1)>   )); //  8, unexpected
printf("%zun", sizeof(std::unique_ptr<int, decltype(x2)>   )); //  8, unexpected
}

最后两个lambda类型的大小为8,即使它们做的和f()相同。

这是怎么做的?

无捕获lambda不需要任何子对象;它只是一个具有CCD_ 2过载的类型。因此,它可以是(但不是必须的(空类型。允许(但不要求(unqiue_ptr优化其"包含"deleter类型的方式,这样,如果deleter类型是空类类型,则它可以使用各种技术来确保此类型不会占用unique_ptr实例本身的存储空间。

有几种方法可以做到这一点。unique_ptr可以从类型继承,依靠EBO来优化基类。使用C++20,它可以将其作为成员子对象,依靠[[no_unique_address]]属性来提供空成员优化。在任何一种情况下,unique_ptr<T>所需要的唯一实际存储是指向T的指针。

相比之下,函数指针就是函数指针。它是一个必须具有存储的基本类型,因为它可以指向任何具有该签名的函数。类型本质上包含要作为类型本身的一部分调用的成员函数;函数指针不会。该类型的实例实际上不需要存储来查找其operator()

取消lambdas并简化decltypes,实际上您写了:

int main(){
struct x1_impl {
void operator()(void *p) { free(p); }
};
x1_impl x1;
struct x2_impl {
void operator()(void *p) { f(p); }
};
x2_impl x2;
printf("%zun", sizeof(std::unique_ptr<int>));
printf("%zun", sizeof(std::unique_ptr<int, void (*)(void*)>));
printf("%zun", sizeof(std::unique_ptr<int, x1_impl>));
printf("%zun", sizeof(std::unique_ptr<int, x2_impl>));
}

CCD_ 11需要同时存储CCD_ 12和CCD_。由于x1_implx2_impl没有数据成员,它们不需要存储,因此最后两个unique_ptr只能存储一个T*。注意,decltype(&f)void (*)(void*),但operator()0是未命名的空struct。Lambdas和函数指针实际上并不相似。对于函数指针,要执行的代码只有在运行时才知道,位于指针后面。对于lambda,要执行的代码在编译时是已知的,lambda对象实际上是闭包,即捕获的变量的集合。这里没有这样的变量,所以Lambda不需要存储任何东西。