ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Windows Terminal 源码实践:接口“纯虚析构函数”模式为何能避免对象销毁时的段错误

Windows Terminal 源码实践:接口“纯虚析构函数”模式为何能避免对象销毁时的段错误 Windows Terminal 源码实践接口“纯虚析构函数”模式为何能避免对象销毁时的段错误【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal本文基于 Windows Terminal/ConPTY 仓库中 doc/virtual-dtors.md 这篇由原作者 Mike Griese 撰写的短文档展开。文档针对一个极易被忽视的 C 接口设计陷阱——析构对象时“接口析构函数替代了基类析构函数”导致的偶发段错误——给出了一种看似矛盾的写法把接口的析构函数声明为纯虚 0之后再显式定义一个空实现。读完本文你将理解这一模式背后的 C 析构语义、它在当前仓库中哪些接口上仍在原样保留、作者又用哪套单元测试持续验证该行为从而在自研 C 项目里做出正确的接口析构设计。文档要解决的问题一个看似多余的析构函数在通读 ConPTYWindows 控制台主机的代码时你会反复遇到这样一种“反直觉”的接口定义文档中给出的原始示例是IRenderDataclass IRenderData { public: virtual ~IRenderData() 0; // methods }; inline IRenderData::~IRenderData() {}作者自己在文档里承认这个模式既反直觉看起来又是多余的——析构函数先被 0声明为纯虚即“删除”了内联实现随后又在类外用inline定义了一个默认空析构两者看似互相矛盾。文档2019-02-20 创建给出的直接结论是如果接口不是严格按这种方式定义对象析构时偶尔会调用接口自身的析构函数而不是预期中基类的析构函数其最终后果是析构对象时偶发段错误segfault。此外2018 年初作者与 austdi 一起排查时还观察到其他“怪异行为”这些细节在文档中没有进一步展开可以推断其具体机理与编译/链接器对纯虚析构的处理方式有关。为什么“纯虚析构 显式定义”是正确的写法要理解文档中这个模式需要回到 C 的析构规则析构调用遵循“从派生到基类”的链式过程。通过基类指针删除派生对象时虚析构保证先运行派生类析构再逐层调用各基类子对象的析构。一旦虚析构缺失删除过程就会直接越过派生部分造成资源泄漏或未定义行为。纯虚析构函数必须有定义。纯虚函数可以没有实现但如果析构链执行到基类子对象的析构而该基类析构被声明为纯虚且从未定义则调用一个“不存在”的函数体是未定义行为——实践中表现为崩溃。标准做法正是文档所示用 0保留“该类是抽象类、不可直接实例化”的语义同时提供inline ~I() default;或空函数体{}作为真实可执行的函数体。定义必须内联在头文件中。接口头文件会被大量编译单元包含如果把这个定义放进某个 .cpp其他编译单元在生成基类析构调用时就会链接失败。文档中inline前缀的作用正在于此。仓库中 ITermDispatch.hpp 完整保留了这一原始形态并附带了非常有价值的一条评论说明团队对该模式“不可随意改动”的态度#pragma warning(push) #pragma warning(disable : 26432) // suppress rule of 5 violation on interface because tampering with this is fraught with peril virtual ~ITermDispatch() 0; // ... 数十个纯虚回调方法 ... }; inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() default; #pragma warning(pop)注意源码中特意用#pragma warning(disable : 26432)抑制了静态分析“rule of 5 违规”告警注释直言“tampering with this is fraught with peril”动它危机四伏。这相当于在代码层面为文档里的警告做了二次背书这个模式是踩过坑之后刻意维持的不能按常规“清理”掉。另一个原样保留该模式的接口是 IStateMachineEngine.hpp它同样声明virtual ~IStateMachineEngine() 0;并在头文件末尾以inline IStateMachineEngine::~IStateMachineEngine() default;给出定义同时把构造函数设为protected以进一步约束实例化路径。与已“现代化”写法的对比virtual ~I() default;值得留意的是当前仓库中不少接口已改用更简洁的等价形式。例如 IRenderData.hppclass IRenderData { public: virtual ~IRenderData() default; virtual Viewport GetViewport() noexcept 0; // ... };从源码结构看IRenderData里所有数据访问方法都是纯虚类依然是抽象类因此文档所述“防止接口被误实例化 提供真实析构函数体”的两个目的都已满足 default形式足够。可以推断仓库经历了从“ 0 out-of-line 定义”到“ default”的写法演进当团队确认某个接口不存在当年那种析构链异常时就可以安全地简化。判断标准应回到文档本身先确保对象析构路径经过充分测试验证再决定能否收敛到更简单的写法而不是反过来为了“代码风格统一”直接批量修改——ITermDispatch上的警告抑制注释正是这种谨慎的物证。顺带说明仓库中还存在第三种形态即普通非纯虚接口直接写virtual ~IFoo() default;如src/interactivity/inc/IConsoleControl.hpp、src/server/IApiRoutines.h等这类接口通常有非纯虚方法或本身允许派生后实例化与文档讨论的“全纯虚回调接口”场景不同读者对照阅读时不必混为一谈。作者如何验证这一行为VtIoTests文档最后一部分指出要检验析构行为是否正确应去读VtIoTests——“该模块里有一大堆测试它们创建对象然后删除对象确保它们永远不会崩溃”。这份测试对应 src/host/ut_host/VtIoTests.cpp。从测试代码可以看出它的验证思路TEST_CLASS_SETUP(ClassSetup)阶段通过CreatePipe建立管道调用VtIo::_Initialize初始化 VT 输出随后用InputStateMachineEngine驱动一屏初始内容s_initialContentVT类内声明了ApiRoutines routines;等成员对象每个TEST_METHOD如SetConsoleCursorPosition、WriteConsoleW、ScrollConsoleScreenBufferW等执行完即随CommonState的 teardown 析构这些对象为了让测试能访问VtIo的私有成员_InitializeVtIo.hpp 专门写了条件友元#ifdef UNIT_TESTING friend class VtIoTests; #endif也就是说作者把“反复创建并销毁 VT 输出/输入相关对象”这件事固化成了回归测试一旦有人“好心”改动了接口析构的写法破坏了析构链这些测试在对象析构时就会以崩溃的形式暴露出来。这正是文档中“以测试兜底”思想的落地——不是靠 review 保证正确而是靠一批持续跑的创建/删除循环来证明正确。实践要点小结结合文档与当前仓库的实际代码可以提炼出三条可操作的准则全纯虚的回调接口如 ITermDispatch.hpp、IStateMachineEngine.hpp 这类把几十上百个方法声明为 0的接口析构务必“纯虚声明 头文件内联定义”二选一写完整切勿留下纯虚而无定义的析构不要批量“清理”这类析构写法。ITermDispatch.hpp中的注释和#pragma warning(disable : 26432)表明该写法是被刻意保护的历史决策改动前应先补齐析构路径的回归测试用“创建—销毁”循环测试兜底。参照 VtIoTests.cpp 的组织方式让单元测试在对象反复构造与析构中运行把文档里那句“确保它们永远不会崩溃”变成 CI 中可执行的断言。需要说明的适用前提本文基于当前仓库Windows Terminal ConPTY 主机同一代码库中该文档及其引用代码的实际状态。文档原文写于 2019 年彼时IRenderData尚是 0形态如今同一接口的析构已收敛为virtual ~IRenderData() default;而ITermDispatch、IStateMachineEngine仍维持原始模式——这一演变本身也印证了文档的主旨析构写法不是风格问题而是需要测试持续担保的稳定性问题。【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表