我正在阅读 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 许可协议
当然是用的。我在我的项目中使用它,几乎在每一堂课中。
使用 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 methodX::foo()
was added toX
andX.h
改变了,尽管XClient1.cpp
由于封装原因不能调用这个方法!像上面一样,它是纯粹的开销,并且与现实生活中的 C++ 构建系统的工作方式有关。当然,当您只修改方法的实现时不需要重新编译(因为您不触摸标题),但这与标准标题/实现技术相当。
这取决于你的目标有多强大。然而,这个问题的唯一答案是:衡量和评估你的得失。此外,请注意,如果您不发布旨在供客户在嵌入式系统中使用的库,则仅适用于编译时间优势!