Why should the “PIMPL” idiom be used? [duplicate]

2018-12-31 08:53发布

This question already has an answer here:

Backgrounder:

The PIMPL Idiom (Pointer to IMPLementation) is a technique for implementation hiding in which a public class wraps a structure or class that cannot be seen outside the library the public class is part of.

This hides internal implementation details and data from the user of the library.

When implementing this idiom why would you place the public methods on the pimpl class and not the public class since the public classes method implementations would be compiled into the library and the user only has the header file?

To illustrate, this code puts the Purr() implementation on the impl class and wraps it as well.

Why not implement Purr directly on the public class?

// header file:
class Cat {
    private:
        class CatImpl;  // Not defined here
        CatImpl *cat_;  // Handle

    public:
        Cat();            // Constructor
        ~Cat();           // Destructor
        // Other operations...
        Purr();
};


// CPP file:
#include "cat.h"

class Cat::CatImpl {
    Purr();
...     // The actual implementation can be anything
};

Cat::Cat() {
    cat_ = new CatImpl;
}

Cat::~Cat() {
    delete cat_;
}

Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
   printf("purrrrrr");
}

11条回答
泪湿衣
2楼-- · 2018-12-31 09:10
  • Because you want Purr() to be able to use private members of CatImpl. Cat::Purr() would not be allowed such an access without a friend declaration.
  • Because you then don't mix responsibilities: one class implements, one class forwards.
查看更多
梦寄多情
3楼-- · 2018-12-31 09:11

For what is worth, it separates the implementation from the interface. This is usually not very important in small size projects. But, in large projects and libraries, it can be used to reduce the build times significantly.

Consider that the implementation of Cat may include many headers, may involve template meta-programming which takes time to compile on its own. Why should a user, who just wants to use the Cat have to include all that? Hence, all the necessary files are hidden using the pimpl idiom (hence the forward declaration of CatImpl), and using the interface does not force the user to include them.

I'm developing a library for nonlinear optimization (read "lots of nasty math"), which is implemented in templates, so most of the code is in headers. It takes about five minutes to compile (on a decent multi-core CPU), and just parsing the headers in an otherwise empty .cpp takes about a minute. So anyone using the library has to wait a couple of minutes every time they compile their code, which makes the development quite tedious. However, by hiding the implementation and the headers, one just includes a simple interface file, which compiles instantly.

It does not necessarily have anything to do with protecting the implementation from being copied by other companies - which wouldn't probably happen anyway, unless the inner workings of your algorithm can be guessed from the definitions of the member variables (if so, it is probably not very complicated and not worth protecting in the first place).

查看更多
临风纵饮
4楼-- · 2018-12-31 09:11

If your class uses the pimpl idiom, you can avoid changing the header file on the public class.

This allows you to add/remove methods to the pimpl class, without modifying the external class's header file. You can also add/remove #includes to the pimpl too.

When you change the external class's header file, you have to recompile everything that #includes it (and if any of those are header files, you have to recompile everything that #includes them, and so on)

查看更多
美炸的是我
5楼-- · 2018-12-31 09:20

We use PIMPL idiom in order to emulate aspect oriented programming where pre, post and error aspects are called before and after the execution of a member function.

struct Omg{
   void purr(){ cout<< "purr\n"; }
};

struct Lol{
  Omg* omg;
  /*...*/
  void purr(){ try{ pre(); omg-> purr(); post(); }catch(...){ error(); } }
};

We also use pointer to base class to share different aspects between many classes.

The drawback of this approach is that the library user has to take into account all the aspects that are going to be executed, but only sees his class. It requires browsing the documentation for any side-effects.

查看更多
牵手、夕阳
6楼-- · 2018-12-31 09:27

I find it telling that, in spite of how well-known the pimpl idiom is, I don't see it crop up very often in real-life (e.g. in open source projects).

I often wonder if the "benefits" are overblown; yes, you can make some of your implementation details even more hidden, and yes, you can change your implementation without changing the header, but it's not obvious that these are big advantages in reality.

That is to say, it's not clear that there's any need for your implementation to be that well hidden, and perhaps it's quite rare that people really do change only the implementation; as soon as you need to add new methods, say, you need to change the header anyway.

查看更多
呛了眼睛熬了心
7楼-- · 2018-12-31 09:28

I think most people refer to this as the Handle Body idiom. See James Coplien's book Advanced C++ Programming Styles and Idioms (Amazon link). It's also known as the Cheshire Cat because of Lewis Caroll's character that fades away until only the grin remains.

The example code should be distributed across two sets of source files. Then only Cat.h is the file that is shipped with the product.

CatImpl.h is included by Cat.cpp and CatImpl.cpp contains the implementation for CatImpl::Purr(). This won't be visible to the public using your product.

Basically the idea is to hide as much as possible of the implementation fom prying eyes. This is most useful where you have a commercial product that is shipped as a series of libraries that are accessed via an API that the customer's code is compiled against and linked to.

We did this with the rewrite of IONAs Orbix 3.3 product in 2000.

As mentioned by others, using his technique completely decouples the implementation from the interface of the object. Then you won't have to recompile everything that uses Cat if you just want to change the implementation of Purr().

This technique is used in a methodology called design by contract.

查看更多
登录 后发表回答