PIMPL 成语真的在实践中使用吗?

新手上路,请多包涵

我正在阅读 Herb Sutter 的 “Exceptional C++” 一书,在那本书中我了解了 PIMPL 习语。基本上,这个想法是为 — 的 class private 对象创建一个结构,并动态分配它们以 减少编译时间(并以更好的方式隐藏私有实现)。

例如:

 class X
{
private:
  C c;
  D d;
} ;

可以改为:

 class X
{
private:
  struct XImpl;
  XImpl* pImpl;
};

并且,在 .cpp 文件中,定义:

 struct X::XImpl
{
  C c;
  D d;
};

这看起来很有趣,但我以前从未见过这种方法,无论是在我工作过的公司中,还是在我看过源代码的开源项目中。所以,我想知道这种技术是否真的在实践中使用。

我应该在任何地方使用它,还是谨慎使用?是否建议将此技术用于嵌入式系统(性能非常重要)?

原文由 Renan Greinert 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 748
2 个回答

所以,我想知道这种技术是否真的在实践中使用?我应该在任何地方使用它,还是谨慎使用?

当然是用的。我在我的项目中使用它,几乎在每一堂课中。


使用 PIMPL 成语的原因:

二进制兼容性

在开发库时,您可以向 XImpl 添加/修改字段,而不会破坏与客户端的二进制兼容性(这意味着崩溃!)。由于 X 类的二进制布局在您向 Ximpl 类添加新字段时不会改变,因此在次要版本更新中向库添加新功能是安全的。

当然,您也可以在不破坏二进制兼容性的情况下向 X / XImpl 添加新的公共/私有非虚拟方法,但这与标准头文件/实现技术相当。

数据隐藏

如果您正在开发一个库,尤其是专有库,最好不要透露使用了哪些其他库/实现技术来实现您的库的公共接口。要么是因为知识产权问题,要么是因为您认为用户可能会倾向于对实现做出危险的假设,或者只是通过使用可怕的铸造技巧来破坏封装。 PIMPL 解决/减轻了这个问题。

编译时间

编译时间减少了,因为当您向 XImpl 类添加/删除字段和/或方法时,只需重建 X 的源(实现)文件(映射到添加标准技术中的私有字段/方法)。在实践中,这是一种常见的操作。

使用标准头/实现技术(没有 PIMPL),当您向 X 添加一个新字段时,每个分配过 X (在堆栈上或堆上)的客户端都需要被重新编译,因为它必须调整分配的大小。好吧,每个不分配 X 的客户端 需要重新编译,但这只是开销(客户端的结果代码将是相同的)。

What is more, with the standard header/implementation separation XClient1.cpp needs to be recompiled even when a private method X::foo() was added to X and X.h 改变了,尽管 XClient1.cpp 由于封装原因不能调用这个方法!像上面一样,它是纯粹的开销,并且与现实生活中的 C++ 构建系统的工作方式有关。

当然,当您只修改方法的实现时不需要重新编译(因为您不触摸标题),但这与标准标题/实现技术相当。


是否建议将此技术用于嵌入式系统(性能非常重要)?

这取决于你的目标有多强大。然而,这个问题的唯一答案是:衡量和评估你的得失。此外,请注意,如果您不发布旨在供客户在嵌入式系统中使用的库,则仅适用于编译时间优势!

原文由 BЈовић 发布,翻译遵循 CC BY-SA 4.0 许可协议

这是我遇到的一个实际场景,这个习语帮助很大。我最近决定在游戏引擎中支持 DirectX 11,以及我现有的 DirectX 9 支持。

引擎已经封装了大部分 DX 功能,因此没有直接使用任何 DX 接口;它们只是在标题中定义为私有成员。该引擎使用 DLL 文件作为扩展,添加键盘、鼠标、操纵杆和脚本支持,与许多其他扩展一样。虽然这些 DLL 中的大多数不直接使用 DX,但它们需要知识和与 DX 的链接,仅仅是因为它们拉入了暴露 DX 的标头。在添加 DX 11 时,这种复杂性会急剧增加,但这是不必要的。将 DX 成员移动到仅在源中定义的 PIMPL 中,消除了这种强制。

除了减少库依赖之外,当我将私有成员函数移到 PIMPL 中时,我暴露的接口变得更清晰,只暴露了前端接口。

原文由 Kit10 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题