当前位置: 首页 > news >正文

强制将析构函数放到类外定义

下面把“为什么/何时需要 force out-of-line dtor(强制将析构函数放到类外定义)”讲清楚,结合 std::unique_ptr 成员的典型情形(PImpl 惯用法),并给出可直接套用的代码模板与替代方案。

1. 结论先行

当你的类在头文件里仅前置声明(forward declare)了被拥有的类型,但成员却是
std::unique_ptr<ThatType> 时,就需要把
析构函数的定义
放到 .cpp(类外、out-of-line)。
原因:std::unique_ptr<T>析构时需要 T完全类型(complete type),而内联(in-class)或头文件中 = default 的析构会在每个包含此头的翻译单元里被实例化,此时若还只有前置声明,就会报错/ODR问题。

2. 出错场景(不要这么写)

头文件只有前置声明,被拥有对象不完整,析构却在头文件内联默认:

// widget.h
#pragma once
#include <memory>
struct Impl;                         // 仅前置声明,未见到完整定义struct Widget {std::unique_ptr<Impl> p_;        // 持有不完整类型~Widget() = default;             // ❌ 在头文件里默认析构(潜在编译或链接问题)// 其它函数...
};

问题本质:~Widget() 的生成位置(头文件处)会让 ~unique_ptr<Impl>() 在各个 TU 被实例化;
default_delete<Impl> 要执行 delete 时,Impl 必须是完整类型,但此时编译器并未见到 Impl 的定义。

3. 正确写法(需要的就是“out-of-line dtor”)

将析构的声明放在头文件,把定义放在 .cpp,并保证 .cpp 里能看到 Impl 的完整定义:

// widget.h
#pragma once
#include <memory>
struct Impl;struct Widget {std::unique_ptr<Impl> p_;~Widget();                       // ✅ 仅声明// 其它函数...
};
// widget.cpp
#include "widget.h"
#include <utility>// 在 .cpp 中提供 Impl 的完整定义(顺序可灵活,核心是此处“完整可见”)
struct Impl {// 真实成员...
};// 在看到 Impl 完整定义之后,再默认析构
Widget::~Widget() = default;         // ✅ out-of-line 定义,满足 unique_ptr 析构对完整类型的要求

要点:只需把 ~Widget() 的定义移到 .cpp,即使是 = default 也行;不必手写释放代码。
这样“强制类外定义”就确保了 unique_ptr 析构点一定能看到 Impl 的完整类型。

4. 何时一定要这么做(判定清单)

  • 成员是 std::unique_ptr<T>,且 T 在头文件只做了前置声明(常见于 PImpl);
  • 你想隐藏实现细节、减少重编译(把 Impl.cpp);
  • 自定义 deleter 也在 .cpp,而头文件中看不到其定义(见下一节的替代解法)。

对比:std::shared_ptr<T> 对不完整类型更宽松,一般不需要 out-of-line dtor(引用计数与销毁点分离),但 unique_ptr 必须在析构点直接 delete,因此严格要求完整类型。

5. 如果“必须头文件内联析构”,可用自定义 deleter兜底

思路:让 unique_ptr 在析构时调用一个类外定义的 deleter;这样即使 ~Widget() 仍在头文件中内联,真正 delete 的动作仍然发生在 .cpp,在那里 Impl 是完整的。

// widget.h
#pragma once
#include <memory>
struct Impl;struct ImplDeleter {void operator()(Impl*) noexcept;     // 仅声明
};struct Widget {// 将 unique_ptr 的第二模板参数设为自定义 deleterstd::unique_ptr<Impl, ImplDeleter> p_;~Widget() = default;                 // ✅ 依然可以在头文件内联析构
};
// widget.cpp
#include "widget.h"// 此处给出 Impl 的完整定义
struct Impl { /* ... */ };// 此处给出 deleter 的完整定义;delete 时 Impl 已完整
void ImplDeleter::operator()(Impl* p) noexcept {delete p;
}

适用:头文件必须 header-only/内联 的库,仍想隐藏 Impl 细节;
代价:unique_ptr 的类型签名更长,可能轻微增加可读性负担。

6. 延伸:还有哪些“看起来没问题但会踩雷”的情况?

  • ~Widget() = default; 写在类外,但放在见不到 Impl 定义.cpp 顶部(或放在包含顺序错误的位置),依旧会报“incomplete type”。
  • 成员是 std::unique_ptr<Impl[]>(数组形式)同样需要完整类型;规则一致。
  • 模板类 template<class T> struct Owner { std::unique_ptr<T> p_; ~Owner() = default; };
    若实例化点看不到 T 的完整定义,依旧会触发同类问题(模板场景更容易在不经意的 TU 里实例化)。

7. 小结(可直接套用的心法)

  • unique_ptr<前置声明类型> ⇒ 析构点必须看到完整类型

  • 两种稳妥做法:

    1. out-of-line dtor:头文件只声明析构,.cpp(见到完整类型后)= default
    2. 自定义 deleter:让真正的 delete.cpp 的 deleter 里执行,从而允许头文件内联析构。
  • 若改用 shared_ptr,通常可避免此约束,但语义/开销不同,请按需求取舍。

http://www.dtcms.com/a/574396.html

相关文章:

  • 虚幻引擎5 GAS开发俯视角RPG游戏 P07-06 能力输入的回调
  • 中企动力做网站贵吗wordpress wp-cumulus
  • 网站没有备案信息该怎么做气象网站建设
  • 6 AutoGen 多 Agent 协作框架:构建智能团队协作系统
  • 昆明做商城网站多少钱网站统计功能设计
  • 优秀个人网站图片如何建立一个小程序的网站
  • 对比28种时间序列预测算法在某个数据集上的表现-全套源码
  • LibreTorrent 4.0.1 | 一款开源磁力软件,不限速,支持RSS
  • 电子商务网站建设与管理的总结做网站用哪种语言好
  • 阿里巴巴网站策划书全球速卖通网址
  • 电子商务网站建设风格seo优化公司
  • 营销网站建设资料扫码支付做进商城网站
  • 10.进程间通信(四)
  • STM32项目分享:智能书桌
  • 做网站怎样做做标书的网站
  • 计算机视觉·LDVC
  • 如何用抽象语法树工具ast解析str格式的文本数据
  • 商务网站开发流程建站之星和凡科
  • 龙岗企业网站建设北京网站开发哪里好薇
  • 宿迁哪里有做网站开发的wordpress 维基
  • 手机号网站源码网站源码本地演示
  • Twitter热点追踪--互动飙升
  • 安徽省级建设主管部门网站网站结构说明
  • 科技设计网站十堰网站建设专家
  • 大恒相机-mono12-python示例程序
  • 线程池和单例模式
  • 建站全过程品牌网站建站
  • Linux之rsyslog(3)模板配置
  • 做网站只用前端知识可以吗热点新闻
  • 免费夸克网盘不限速下载简单方法