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 21:53

As many other said, the Pimpl idiom allows to reach complete information hiding and compilation independency, unfortunately with the cost of performance loss (additional pointer indirection) and additional memory need (the member pointer itself). The additional cost can be critical in embedded software development, in particular in those scenarios where memory must be economized as much as possible. Using C++ abstract classes as interfaces would lead to the same benefits at the same cost. This shows actually a big deficiency of C++ where, without recurring to C-like interfaces (global methods with an opaque pointer as parameter), it is not possible to have true information hiding and compilation independency without additional resource drawbacks: this is mainly because the declaration of a class, which must be included by its users, exports not only the interface of the class (public methods) needed by the users, but also its internals (private members), not needed by the users.

查看更多
公子世无双
3楼-- · 2018-12-31 21:59

It seems that a lot of libraries out there use it to stay stable in their API, at least for some versions.

But as for all things, you should never use anything everywhere without caution. Always think before using it. Evaluate what advantages it gives you, and if they are worth the price you pay.

The advantages it may give you are:

  • helps in keeping binary compatibility of shared libraries
  • hiding certain internal details
  • decreasing recompilation cycles

Those may or may not be real advantages to you. Like for me, I don't care about a few minutes recompilation time. End users usually also don't, as they always compile it once and from the beginning.

Possible disadvantages are (also here, depending on the implementation and whether they are real disadvantages for you):

  • Increase in memory usage due to more allocations than with the naïve variant
  • increased maintenance effort (you have to write at least the forwarding functions)
  • performance loss (the compiler may not be able to inline stuff as it is with a naïve implementation of your class)

So carefully give everything a value, and evaluate it for yourself. For me, it almost always turns out that using the pimpl idiom is not worth the effort. There is only one case where I personally use it (or at least something similar):

My C++ wrapper for the linux stat call. Here the struct from the C header may be different, depending on what #defines are set. And since my wrapper header can't control all of them, I only #include <sys/stat.h> in my .cxx file and avoid these problems.

查看更多
何处买醉
4楼-- · 2018-12-31 22:01

One benefit I can see is that it allows the programmer to implement certain operations in a fairly fast manner:

X( X && move_semantics_are_cool ) : pImpl(NULL) {
    this->swap(move_semantics_are_cool);
}
X& swap( X& rhs ) {
    std::swap( pImpl, rhs.pImpl );
    return *this;
}
X& operator=( X && move_semantics_are_cool ) {
    return this->swap(move_semantics_are_cool);
}
X& operator=( const X& rhs ) {
    X temporary_copy(rhs);
    return this->swap(temporary_copy);
}

PS: I hope I'm not misunderstanding move semantics.

查看更多
牵手、夕阳
5楼-- · 2018-12-31 22:02

I think this is one of the most fundamental tools for decoupling.

I was using pimpl (and many other idioms from Exceptional C++) on embedded project (SetTopBox).

The particular purpose of this idoim in our project was to hide the types XImpl class uses. Specifically we used it to hide details of implementations for different hardware, where different headers would be pulled in. We had different implementations of XImpl classes for one platform and different for the other. Layout of class X stayed the same regardless of the platfrom.

查看更多
与君花间醉酒
6楼-- · 2018-12-31 22:05

It is used in practice in a lot of projects. It's usefullness depends heavily on the kind of project. One of the more prominent projects using this is Qt, where the basic idea is to hide implementation or platformspecific code from the user (other developers using Qt).

This is a noble idea but there is a real drawback to this: debugging As long as the code hidden in private implemetations is of premium quality this is all well, but if there are bugs in there, then the user/developer has a problem, because it just a dumb pointer to a hidden implementation, even if he has the implementations source code.

So as in nearly all design decisions there a pros and cons.

查看更多
零度萤火
7楼-- · 2018-12-31 22:06

Here is an actual scenario I encountered, where this idiom helped a great deal. I recently decided to support DirectX 11, as well as my existing DirectX 9 support, in a game engine. The engine already wrapped most DX features, so none of the DX interfaces were used directly; they were just defined in the headers as private members. The engine utilizes DLLs as extensions, adding keyboard, mouse, joystick, and scripting support, as week as many other extensions. While most of those DLLs did not use DX directly, they required knowledge and linkage to DX simply because they pulled in headers that exposed DX. In adding DX 11, this complexity was to increase dramatically, however unnecessarily. Moving the DX members into a Pimpl defined only in the source eliminated this imposition. On top of this reduction of library dependencies, my exposed interfaces became cleaner as moved private member functions into the Pimpl, exposing only front facing interfaces.

查看更多
登录 后发表回答