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:28

I don't know if this is a difference worth mentioning but...

Would it be possible to have the implementation in its own namespace and have a public wrapper / library namespace for the code the user sees:

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

This way all library code can make use of the cat namespace and as the need to expose a class to the user arises a wrapper could be created in the catlib namespace.

查看更多
冷夜・残月
3楼-- · 2018-12-31 09:29

Placing the call to the impl->Purr inside the cpp file means that in the future you could do something completely different without having to change the header file. Maybe next year they discover a helper method they could have called instead and so they can change the code to call that directly and not use impl->Purr at all. (Yes, they could achieve the same thing by updating the actual impl::Purr method as well but in that case you are stuck with an extra function call that achieves nothing but calling the next function in turn)

It also means the header only has definitions and does not have any implementation which makes for a cleaner separation, which is the whole point of the idiom.

查看更多
孤独总比滥情好
4楼-- · 2018-12-31 09:32

Well, I wouldn't use it. I have a better alternative:

foo.h:

class Foo {
public:
    virtual ~Foo() { }
    virtual void someMethod() = 0;

    // This "replaces" the constructor
    static Foo *create();
}

foo.cpp:

namespace {
    class FooImpl: virtual public Foo {

    public:
        void someMethod() { 
            //....
        }     
    };
}

Foo *Foo::create() {
    return new FooImpl;
}

Does this pattern have a name?

As an also Python and Java programmer, I like this a lot more than the pImpl idiom.

查看更多
看风景的人
5楼-- · 2018-12-31 09:35

Typically, the only reference to Pimpl class in the header for the Owner class (Cat in this case) would be a forward declaration, as you have done here, because that can greatly reduce the dependencies.

For example, if your Pimpl class has ComplicatedClass as a member (and not just a pointer or reference to it) then you would need to have ComplicatedClass fully defined before it's use. In practice, this means including "ComplicatedClass.h" (which will also indirectly include anything ComplicatedClass depends on). This can lead to a single header fill pulling in lots and lots of stuff, which is bad for managing your dependencies (and your compile times).

When you use the pimpl idion, you only need to #include the stuff used in the public interface of your Owner type (which would be Cat here). Which makes things better for people using your library, and means you don't need to worry about people depending on some internal part of your library - either by mistake, or because they want to do something you don't allow so they #define private public before including your files.

If it's a simple class, there's usually no reason to use a Pimpl, but for times when the types are quite big, it can be a big help (especially in avoiding long build times)

查看更多
路过你的时光
6楼-- · 2018-12-31 09:37

I just implemented my first pimpl class over the last couple of days. I used it to eliminate problems I was having including winsock2.h in Borland Builder. It seemed to be screwing up struct alignment and since I had socket things in the class private data, those problems were spreading to any cpp file that included the header.

By using pimpl, winsock2.h was included in only one cpp file where I could put a lid on the problem and not worry that it would come back to bite me.

To answer the original question, the advantage I found in forwarding the calls to the pimpl class was that the pimpl class is the same as what your original class would have been before you pimpl'd it, plus your implementations aren't spread over 2 classes in some weird fashion. It's much clearer to implement the publics to simply forward to the pimpl class.

Like Mr Nodet said, one class, one responsibility.

查看更多
登录 后发表回答