Pimpl成语在实践中

Rya*_*rle 47 c++ pimpl-idiom

关于pimpl成语有一些关于SO的问题,但我更加好奇它在实践中的使用频率.

我理解在性能和封装之间存在一些权衡,加上一些调试烦恼,因为额外的重定向.

有了这个,是应该采用每个类,还是全有或全无的方式?这是最佳做法还是个人偏好?

我意识到这有点主观,所以让我列出我的首要任务:

  • 代码清晰度
  • 代码可维护性
  • 性能

我总是假设我需要在某些时候将代码作为库公开,所以这也是一个考虑因素.

编辑:任何其他选项来完成相同的事情将是受欢迎的建议.

Reu*_*nen 36

我会说,无论你是按班级还是全部或全无,取决于你为什么要首先选择pimpl成语.在构建库时,我的理由是以下之一:

  • 想隐藏实施以避免泄露信息(是的,这不是一个FOSS项目:)
  • 希望隐藏实现以使客户端代码更少依赖.如果您构建共享库(DLL),则可以在不重新编译应用程序的情况下更改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的情况下执行更容易的更改.

  • 优点.我会使用C函数并显式传递pimpl指针作为每个方法调用的第一个参数而不是C++.C(最好通过动态库)在不同的编译器和环境之间比C++更兼容. (2认同)

wil*_*ell 12

代码清晰度

代码清晰度是非常主观的,但在我看来,具有单个数据成员的标题比具有许多数据成员的标题更具可读性.然而,实现文件更嘈杂,因此清晰度降低.如果类是基类,那么这可能不是问题,主要由派生类使用而不是维护.

可维护性

为了保持pimpl'd类的可维护性,我个人发现每次访问数据成员时都需要额外的解除引用.如果数据纯粹是私有的,那么访问者就无法帮助,因为无论如何你都不应该为它公开一个访问器或者mutator,并且你总是不停地解除引用pimpl.

对于派生类的可维护性,我发现在所有情况下,成语都是纯粹的胜利,因为头文件列出了更少的无关细节.所有客户端编译单元的编译时间也得到了改进.

性能

在许多情况下,性能损失很小,而在很少的情 从长远来看,它是虚拟功能性能损失的数量级.我们谈论的是每个数据成员每次访问的额外取消引用,以及pimpl的动态内存分配,以及销毁时的内存释放.如果pimpl'd类经常不访问其数据成员,则pimpl'd类的对象经常被创建并且是短暂的,然后动态分配可以超出额外的解除引用.

决策

我认为性能至关重要的类,使得一个额外的解引用或内存分配产生显着差异,不管怎么说都不应该使用pimpl.如果编译时间得到显着改善,那么性能降低无关紧要并且头文件广泛#include'd的基础classe可能应该使用pimpl.如果编译时间没有减少,那就是你的代码清晰度.

对于所有其他情况,这纯粹是一种品味问题.在做出决定之前,请尝试并测量运行时性能和编译时性能.


Ste*_*sop 7

当您使用强异常保证来实现std :: swap和operator =时,pImpl非常有用.我倾向于说,如果你的班级支持其中任何一个,并且有不止一个非平凡的领域,那么它通常不再是偏好.

否则,它是关于您希望客户端通过头文件绑定到实现的紧密程度.如果二进制不兼容的更改不是问题,那么您可能无法在可维护性方面获得太多好处,尽管如果编译速度成为一个问题,通常会有节省.

性能成本可能更多地与内联丢失有关,而不是间接性,但这是一个疯狂的猜测.

您可以随时添加pImpl,并声明从今天起,客户端不必仅因为添加了私有字段而重新编译.

所以这些都没有暗示一种全有或全无的方法.你可以有选择地为那些给你带来好处的课程做这件事,而不是为那些没有给你带来好处的课程,并在以后改变你的想法.像pImpl一样实现迭代器听起来像Too Much Design ......


Gre*_*egC 5

这个成语在大型项目的编译时间上有很大帮助。

外部链接

这也很好