关于pimpl成语有一些关于SO的问题,但我更加好奇它在实践中的使用频率.
我理解在性能和封装之间存在一些权衡,加上一些调试烦恼,因为额外的重定向.
有了这个,是应该采用每个类,还是全有或全无的方式?这是最佳做法还是个人偏好?
我意识到这有点主观,所以让我列出我的首要任务:
我总是假设我需要在某些时候将代码作为库公开,所以这也是一个考虑因素.
编辑:任何其他选项来完成相同的事情将是受欢迎的建议.
Reu*_*nen 36
我会说,无论你是按班级还是全部或全无,取决于你为什么要首先选择pimpl成语.在构建库时,我的理由是以下之一:
这些原因都没有提示采用全有或全无的方法.在第一个中,你只是隐藏你想要隐藏的内容,而在第二种情况下,对于你期望改变的类来说,它可能就足够了.同样出于第三和第四个原因,只有隐藏非平凡成员才能获益,而这些成员又需要额外的标题(例如,第三方库,甚至是STL).
无论如何,我的观点是我通常不会发现这样的东西太有用了:
class Point {
public:
Point(double x, double y);
Point(const Point& src);
~Point();
Point& operator= (const Point& rhs);
void setX(double x);
void setY(double y);
double getX() const;
double getY() const;
private:
class PointImpl;
PointImpl* pimpl;
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,权衡开始打击你,因为指针需要被解引用,并且方法不能内联.但是,如果仅对非平凡类进行此操作,则通常可以容忍轻微的开销而没有任何问题.
Art*_*yom 22
pimpl ideom的最大用途之一是创建稳定的C++ ABI.几乎每个 Qt类都使用"D"指针,这是一种pimpl.这样可以在不破坏ABI的情况下执行更容易的更改.
wil*_*ell 12
代码清晰度是非常主观的,但在我看来,具有单个数据成员的标题比具有许多数据成员的标题更具可读性.然而,实现文件更嘈杂,因此清晰度降低.如果类是基类,那么这可能不是问题,主要由派生类使用而不是维护.
为了保持pimpl'd类的可维护性,我个人发现每次访问数据成员时都需要额外的解除引用.如果数据纯粹是私有的,那么访问者就无法帮助,因为无论如何你都不应该为它公开一个访问器或者mutator,并且你总是不停地解除引用pimpl.
对于派生类的可维护性,我发现在所有情况下,成语都是纯粹的胜利,因为头文件列出了更少的无关细节.所有客户端编译单元的编译时间也得到了改进.
在许多情况下,性能损失很小,而在很少的情 从长远来看,它是虚拟功能性能损失的数量级.我们谈论的是每个数据成员每次访问的额外取消引用,以及pimpl的动态内存分配,以及销毁时的内存释放.如果pimpl'd类经常不访问其数据成员,则pimpl'd类的对象经常被创建并且是短暂的,然后动态分配可以超出额外的解除引用.
我认为性能至关重要的类,使得一个额外的解引用或内存分配产生显着差异,不管怎么说都不应该使用pimpl.如果编译时间得到显着改善,那么性能降低无关紧要并且头文件广泛#include'd的基础classe可能应该使用pimpl.如果编译时间没有减少,那就是你的代码清晰度.
对于所有其他情况,这纯粹是一种品味问题.在做出决定之前,请尝试并测量运行时性能和编译时性能.
当您使用强异常保证来实现std :: swap和operator =时,pImpl非常有用.我倾向于说,如果你的班级支持其中任何一个,并且有不止一个非平凡的领域,那么它通常不再是偏好.
否则,它是关于您希望客户端通过头文件绑定到实现的紧密程度.如果二进制不兼容的更改不是问题,那么您可能无法在可维护性方面获得太多好处,尽管如果编译速度成为一个问题,通常会有节省.
性能成本可能更多地与内联丢失有关,而不是间接性,但这是一个疯狂的猜测.
您可以随时添加pImpl,并声明从今天起,客户端不必仅因为添加了私有字段而重新编译.
所以这些都没有暗示一种全有或全无的方法.你可以有选择地为那些给你带来好处的课程做这件事,而不是为那些没有给你带来好处的课程,并在以后改变你的想法.像pImpl一样实现迭代器听起来像Too Much Design ......