Ren*_*ert 157 c++ oop pimpl-idiom
我正在阅读Herb Sutter的"Exceptional C++"一书,在那本书中我学到了关于pImpl的习语.基本上,我们的想法是为a的private对象创建一个结构class并动态分配它们以减少编译时间(并且还以更好的方式隐藏私有实现).
例如:
class X
{
private:
C c;
D d;
} ;
Run Code Online (Sandbox Code Playgroud)
可以改为:
class X
{
private:
struct XImpl;
XImpl* pImpl;
};
Run Code Online (Sandbox Code Playgroud)
并且,在CPP中,定义:
struct X::XImpl
{
C c;
D d;
};
Run Code Online (Sandbox Code Playgroud)
这看起来很有趣,但我以前从未见过这种方法,既没有在我工作的公司,也没有在我看过源代码的开源项目中.所以,我想知道这种技术真的在实践中使用了吗?
我应该在任何地方使用它,还是谨慎使用?这种技术是否建议用于嵌入式系统(性能非常重要)?
BЈо*_*вић 124
所以,我想知道这种技术真的在实践中使用了吗?我应该在任何地方使用它,还是谨慎使用?
当然,在你提到的几个原因中,几乎在每个班级中都使用了它,在我的项目中使用了它:
这种技术是否被推荐用于嵌入式系统(性能非常重要)?
这取决于你的目标有多强大.然而,这个问题的唯一答案是:衡量和评估你获得和失去的东西.
Pla*_*aHH 46
似乎有很多库使用它来保持其API的稳定性,至少对于某些版本而言.
但至于所有事情,你绝不应该谨慎使用任何地方.在使用它之前一定要考虑.评估它给你带来的好处,以及它们是否物有所值.
它可能给你带来的好处是:
那些对你来说可能是也可能不是真正的优势.对我来说,我不关心几分钟的重新编译时间.最终用户通常也不会,因为他们总是从头开始编译它.
可能的缺点是(在这里,取决于实现以及它们对您来说是否真正的缺点):
所以要小心地给所有东西一个价值,并为自己评估.对我来说,几乎总是证明使用pimpl成语并不值得付出努力.只有一个案例我个人使用它(或至少类似的东西):
我的C++包装器用于linux stat调用.这里C头的结构可能不同,具体取决于#defines设置的内容.由于我的包装器头不能控制所有这些,我只#include <sys/stat.h>在我的.cxx文件中避免这些问题.
Emi*_*lia 29
同意所有其他有关货物的信息,但让我提出一个限制:与模板不兼容.
原因是模板实例化需要在实例化发生的地方提供完整的声明.(这是您没有看到CPP文件中定义的模板方法的主要原因)
您仍然可以参考模板化子类,但由于您必须将它们全部包含在内,因此编译时"实现解耦"的所有优点(避免在所有地方包含所有平台特定代码,缩短编译)都会丢失.
对于经典OOP(基于继承)而言,它是一个很好的范例,但对于通用编程(基于特化)则不是.
小智 21
其他人已经提供了技术上/下行,但我认为以下值得注意:
首先,不要教条.如果pImpl适用于您的情况,请使用它 - 不要仅仅因为"它更好的OO,因为它真的隐藏了实现"等等.引用C++ FAQ:
封装是代码,而不是人(源)
只是为了举例说明使用它的开源软件及其原因:OpenThreads,OpenSceneGraph使用的线程库.主要思想是从标题中删除(例如<Thread.h>)所有特定于平台的代码,因为内部状态变量(例如,线程句柄)因平台而异.这样就可以在不知道其他平台特性的情况下针对您的库编译代码,因为一切都是隐藏的.
Ghi*_*ita 12
我主要考虑将PIMPL用于其他模块用作API的类.这有很多好处,因为它使重新编译PIMPL实现中所做的更改不会影响项目的其余部分.此外,对于API类,它们提升了二进制兼容性(模块实现中的更改不会影响这些模块的客户端,因为新实现具有相同的二进制接口 - PIMPL公开的接口,所以不必重新编译它们).
至于在每个类中使用PIMPL,我会考虑谨慎,因为所有这些好处都需要付出代价:为了访问实现方法,需要额外的间接级别.
我认为这是解耦的最基本工具之一.
我在嵌入式项目(SetTopBox)上使用了pimpl(和Exceptional C++的许多其他成语).
我们项目中这个idoim的特殊目的是隐藏XImpl类使用的类型.具体来说,我们使用它来隐藏不同硬件的实现细节,其中将引入不同的标头.我们对一个平台有不同的XImpl类实现,而另一个平台有不同的实现.无论平台如何,X级的布局都保持不变.
过去我经常使用这种技术,但后来发现自己逐渐远离它。
当然,对类的用户隐藏实现细节是个好主意。但是,您也可以通过让类的用户使用抽象接口并将实现细节作为具体类来做到这一点。
pImpl 的优点是:
假设这个接口只有一个实现,不使用抽象类/具体实现会更清楚
如果您有一组类(一个模块),以便多个类访问相同的“impl”,但该模块的用户将只使用“公开的”类。
如果这被认为是一件坏事,则没有 v-table。
我发现 pImpl 的缺点(抽象接口效果更好)
虽然您可能只有一个“生产”实现,但通过使用抽象接口,您还可以创建一个在单元测试中工作的“模拟”实现。
(最大的问题)。在 unique_ptr 和 move 出现之前,您对如何存储 pImpl 的选择受到限制。一个原始指针,你有关于你的类不可复制的问题。旧的 auto_ptr 不适用于前向声明的类(无论如何不适用于所有编译器)。所以人们开始使用 shared_ptr ,这很好地使您的类可复制,但当然,两个副本都具有您可能不期望的相同底层 shared_ptr (修改一个,两个都被修改)。因此,解决方案通常是对内部指针使用原始指针,并使类不可复制并返回一个 shared_ptr 来代替。所以两次调用new。(实际上 3 给了旧的 shared_ptr 给了你第二个)。
从技术上讲并不是真正的常量正确,因为常量不会传播到成员指针。
总的来说,这些年来我已经从 pImpl 转移到抽象接口的使用(以及创建实例的工厂方法)。