跟进这个问题,
How can std::unique_ptr have no size overhead?
我遵循了代码,结果在某种程度上是出乎意料的:
#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("%zu\n", sizeof(std::unique_ptr<int> )); // 8, expected
printf("%zu\n", sizeof(std::unique_ptr<int, decltype(&f)> )); // 16, expected
printf("%zu\n", sizeof(std::unique_ptr<int, decltype(x1)> )); // 8, unexpected
printf("%zu\n", sizeof(std::unique_ptr<int, decltype(x2)> )); // 8, unexpected
}最后两种类型的lambda大小为8,甚至与f()相同。
这是怎么做的?
发布于 2019-11-01 23:41:47
无捕获的lambda不需要任何子对象;它只是一个具有operator()重载的类型。因此,它可以是(但不需要是)空类型。允许(但不需要) unqiue_ptr优化其“包含”删除类型的方式,以便如果删除类型是空类类型,那么它可以使用各种技术来确保该类型不占用unique_ptr实例本身内的存储。
有几种方法可以做到这一点。unique_ptr可以从类型继承,依靠EBO优化基类。使用C++20,它可以使它成为一个子对象,依赖于[[no_unique_address]]属性来提供空成员优化。在这两种情况下,unique_ptr<T>所需要的唯一实际存储就是指向T的指针。
相反,函数指针就是函数指针。它是一个必须有存储的基本类型,因为它可以指向任何具有该签名的函数。类型本质上包含要作为类型本身的一部分调用的成员函数;函数指针不包含。该类型的实例实际上不需要存储来找到它的operator()。
发布于 2019-11-01 23:40:15
对于lambdas和简化decltype,您实际上是这样写的:
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("%zu\n", sizeof(std::unique_ptr<int>));
printf("%zu\n", sizeof(std::unique_ptr<int, void (*)(void*)>));
printf("%zu\n", sizeof(std::unique_ptr<int, x1_impl>));
printf("%zu\n", sizeof(std::unique_ptr<int, x2_impl>));
}unique_ptr<T, Del>需要同时存储T*和Del。因为x1_impl和x2_impl没有数据成员,所以它们不需要存储,所以最后两个unique_ptr可以只存储一个T*。注意,decltype(&f)是void (*)(void*),但是decltype(x1)是一个未命名的空struct。Lambda和函数指针实际上没有什么相似之处。对于函数指针,只有在运行时才知道要执行的代码,它位于指针后面。对于lambda,要执行的代码在编译时是已知的,lambda对象实际上是闭包,是捕获变量的集合。在这里,没有这样的变量,因此lambda不需要存储任何内容。
https://stackoverflow.com/questions/58666958
复制相似问题