如何创建带有编译期静态删除器的std::unique_ptr(无需运行时函数指针)
你的需求完全可以实现!核心要点是:让删除器的类型是一个无状态的可调用类型(比如无捕获Lambda、空函数对象类),而不是函数指针类型——后者会迫使std::unique_ptr额外存储删除器指针(导致大小翻倍),而无状态的可调用类型可以被编译器优化,不需要存储任何额外数据,完美契合你"编译期确定删除器、分配时无需传函数指针"的需求。
先拆解你最初的测试代码困惑
Lambda删除器的成功案例
你第一个测试里的无捕获Lambda,它的类型是一个编译器生成的空类(无状态)。当你用decltype(deleter)作为unique_ptr的删除器模板参数时,unique_ptr不需要存储这个删除器的实例——因为删除器的行为是编译期完全确定的,编译器可以直接调用Lambda的operator(),不需要额外存储任何东西,所以ptr1的大小是8字节(和原始指针一致),而且构造时也不用传删除器参数。函数指针删除器的问题
第二个测试里你用了decltype(&staticDeleter),这本质是函数指针类型。函数指针是运行时的值(哪怕你用的是静态函数,类型依然是指针),所以unique_ptr必须存储这个指针才能在析构时调用,这就是为什么ptr2大小是16字节,而且构造时必须显式传递staticDeleter——因为函数指针类型没有默认构造函数,编译器不知道你要指向哪个函数。
你修正后的代码为什么有效?
你后来在Container里用了一个无捕获Lambda作为删除器类型:
using deleter = decltype([](Apa* apa) { std::cout << "deleter\n"; delete apa; });
这个Lambda是无捕获的,它的类型是一个空的类类型,完全符合unique_ptr的优化条件。所以std::unique_ptr<Apa, deleter>不需要存储删除器实例,大小保持8字节,而且构造时可以直接用new Apa初始化,不需要额外传递删除器——因为编译器可以默认构造这个无状态的删除器类型。
另一种实现方式:自定义空删除器类
如果你不想用Lambda,也可以手动定义一个无状态的函数对象类,效果完全一样:
#include <memory> #include <iostream> struct Apa{ ~Apa() { std::cout << "Apa deleted\n"; } }; // 自定义静态删除器类,无状态 struct StaticDeleter { void operator()(Apa* a) const { std::cout << "static deleter\n"; delete a; } }; struct Container { using deleter = StaticDeleter; // 构造时无需传递删除器,编译器会默认构造StaticDeleter std::unique_ptr<Apa, deleter> ptr{new Apa}; }; int main() { std::cout << sizeof(Container::ptr) << "\n"; Container container{}; }
这段代码的输出和你修正后的Lambda版本完全一致,unique_ptr的大小也是8字节。
总结
只要满足以下两个条件,就能实现你想要的效果:
- 删除器类型是无状态的(没有非静态成员变量,比如无捕获Lambda、空函数对象类)
- 删除器类型是可默认构造的
此时std::unique_ptr会在编译期确定删除器的行为,不需要存储任何删除器实例,分配内存时也无需传递删除器指针,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Lasersköld

