Is the pImpl idiom really used in practice?

2018-12-31 21:28发布

I am reading the book "Exceptional C++" by Herb Sutter, and in that book I have learned about the pImpl idiom. Basically, the idea is to create a structure for the private objects of a class and dynamically allocate them to decrease the compilation time (and also hide the private implementations in a better manner).

For example:

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

could be changed to:

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

and, in the CPP, the definition:

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

This seems pretty interesting, but I have never seen this kind of approach before, neither in the companies I have worked, nor in open source projects that I've seen the source code. So, I am wondering it this technique is really used in practice?

Should I use it everywhere, or with caution? And is this technique recommended to be used in embedded systems (where the performance is very important)?

11条回答
ら面具成の殇う
2楼-- · 2018-12-31 22:14

Agree with all the others about the goods, but let me put in evidence a limit: doesn't work well with templates.

The reason is that template instantiation requires the full declaration available where the instantiation took place. (And that's the main reason you don't see template methods defined into CPP files)

You can still refer to templetised subclasses, but since you have to include them all, every advantage of "implementation decoupling" on compiling (avoiding to include all platoform specific code everywhere, shortening compilation) is lost.

Is a good paradigm for classic OOP (inheritance based) but not for generic programming (specialization based).

查看更多
梦寄多情
3楼-- · 2018-12-31 22:14

I used to use this technique a lot in the past but then found myself moving away from it.

Of course it is a good idea to hide the implementation detail away from the users of your class. However you can also do that by getting users of the class to use an abstract interface and for the implementation detail to be the concrete class.

The advantages of pImpl are:

  1. Assuming there is just one implementation of this interface, it is clearer by not using abstract class / concrete implementation

  2. If you have a suite of classes (a module) such that several classes access the same "impl" but users of the module will only use the "exposed" classes.

  3. No v-table if this is assumed to be a bad thing.

The disadvantages I found of pImpl (where abstract interface works better)

  1. Whilst you may have only one "production" implementation, by using an abstract interface you can also create a "mock" inmplementation that works in unit testing.

  2. (The biggest issue). Before the days of unique_ptr and moving you had restricted choices as to how to store the pImpl. A raw pointer and you had issues about your class being non-copyable. An old auto_ptr wouldn't work with forwardly declared class (not on all compilers anyway). So people started using shared_ptr which was nice in making your class copyable but of course both copies had the same underlying shared_ptr which you might not expect (modify one and both are modified). So the solution was often to use raw pointer for the inner one and make the class non-copyable and return a shared_ptr to that instead. So two calls to new. (Actually 3 given old shared_ptr gave you a second one).

  3. Technically not really const-correct as the constness isn't propagated through to a member pointer.

In general I have therefore moved away in the years from pImpl and into abstract interface usage instead (and factory methods to create instances).

查看更多
听够珍惜
4楼-- · 2018-12-31 22:15

I would mainly consider PIMPL for classes exposed to be used as an API by other modules. This has many benefits, as it makes recompilation of the changes made in the PIMPL implementation does not affect the rest of the project. Also, for API classes they promote a binary compatibility (changes in a module implementation do not affect clients of those modules, they don't have to be recompiled as the new implementation has the same binary interface - the interface exposed by the PIMPL).

As for using PIMPL for every class, I would consider caution because all those benefits come at a cost: an extra level of indirection is required in order to access the implementation methods.

查看更多
查无此人
5楼-- · 2018-12-31 22:17

So, I am wondering it this technique is really used in practice? Should I use it everywhere, or with caution?

Of course it is used, and in my project, in almost every class, for several reasons you mentioned :

  • data hiding
  • recompilation time is really decreased, since only the source file needs to be rebuilt, but not the header, and every file that includes it
  • binary compatibility. Since the class declaration doesn't change, it is safe to just update the library (assuming you are creating a library)

is this technique recommended to be used in embedded systems (where the performance is very important)?

That depends on how powerful your target is. However the only answer to this question is : measure and evaluate what you gain and lose.

查看更多
千与千寻千般痛.
6楼-- · 2018-12-31 22:20

Other people have already provided the technical up/downsides, but I think the following is worth noting:

First and foremost, don't be dogmatic. If pImpl works for your situation, use it - don't use it just because "it's better OO since it really hides implementation" etc. Quoting the C++ FAQ:

encapsulation is for code, not people (source)

Just to give you an example of open source software where it is used and why: OpenThreads, the threading library used by the OpenSceneGraph. The main idea is to remove from the header (e.g. <Thread.h>) all platform-specific code, because internal state variables (e.g. thread handles) differ from platform to platform. This way one can compile code against your library without any knowledge of the other platforms' idiosyncrasies, because everything is hidden.

查看更多
登录 后发表回答