C++ Pimpl惯用法下缺省外层类析构函数报无法删除不完整类型疑问
核心概念解释
首先明确完整类型和不完整类型的定义:
- 完整类型:编译器已经明确知晓其内存布局、成员定义、占用空间大小的类型,比如完全定义完成的
class/struct、int/float这类基础数据类型都属于完整类型 - 不完整类型:仅做了前置声明、没有给出完整实现定义的类型,你代码头文件中的
struct impl;就是典型的不完整类型,此时编译器不知道impl的大小、成员、析构函数位置等关键信息。
std::unique_ptr的删除逻辑
std::unique_ptr的默认删除器是std::default_delete<T>,这个删除器执行delete操作的前提是T必须是完整类型:如果T是不完整类型,std::default_delete的调用会直接触发编译错误,也就是你遇到的can't delete an incomplete type报错。
不声明employee析构函数报错的原因
如果你没有在employee类中显式声明析构函数,编译器会在头文件的类定义位置自动生成内联的默认析构函数。这个自动生成的析构函数执行时需要销毁类的所有成员变量,也就是你的unique_ptr<impl> pimpl。
但此时头文件的上下文里impl还是不完整类型,编译器看不到impl的完整定义,所以默认析构函数中调用的unique_ptr删除逻辑就会触发类型不完整的编译错误。
显式声明析构函数的作用
当你在头文件中仅声明~employee();,把析构函数的实现放在employee.cpp中(也就是你注释的employee::~employee() = default;这行),逻辑就变成了:
- 编译器生成
employee析构函数的时机被推迟到了cpp文件编译阶段 - cpp文件中已经有
struct employee::impl的完整定义,此时编译器知晓impl的全部信息,调用unique_ptr的删除器完全合法,自然不会报错。
补充说明
impl自身是否定义析构函数和这个报错没有关系,问题的核心不是impl有没有析构函数,而是调用impl删除逻辑的代码所在的位置,能不能看到impl的完整定义。
你遇到的拷贝/赋值运算符需要手动实现的规则也是同理:编译器默认生成的拷贝/赋值逻辑会在头文件上下文生成代码,一方面unique_ptr本身不支持默认拷贝,另一方面也会遇到impl类型不完整的问题,所以必须手动在cpp文件中实现相关逻辑。
内容的提问来源于stack exchange,提问作者keepGoing

