PIMPL idiom VS forward declaration

2019-08-12 12:28发布

问题:

I have read a bit about the PIMPL idiom and was wondering - is it any different to forward declaring the dependent type(s)?

If so:

  • When will I prefer using that over a forward declaration?
  • Do these two versions differ in their compilation time?
  • Is one of them more scalable than the other?

Specifically consider a class Foo that is dependent on Bar (should have a member of type Bar).

Foo.h with forward declaration:

class Bar;

class Foo
{
public:
    Foo();

private:
    Bar* _bar;
};

Foo.h with PIMPL:

class Foo
{
    public:
        Foo();

    private:
        /* FooImpl is an incomplete type at this point.
         * Implemented in cpp file and has a member of type Bar.
         */
        class FooImpl;  

        FooImpl* _fooImpl;
}

Please ignore the raw pointer usage - I was just trying to make a point.

回答1:

I have read a bit about the PIMPL idiom and was wondering - is it any different to forward declaring the dependent type(s)?

Yes, they are different. The PIMPL idiom (it has several names) is specifically about hiding the implementation detail from the client code. This could be done for a number of reasons, including (but not limited to);

  • isolating the rebuild when class details change (they are hidden)
  • minimising required or conflicting header inclusions
  • a general build time reduction
  • minimal export requirements (although abstract classes could be used for this purpose as well)
  • easier control over implementation detail that varies over multiple targets or platforms

In essence, the PIMPL offers a technique to "hide" the implementation from the client code - whenever that may be needed.

When will I prefer using [PIMPL] over forward declaration?

This is really about intent - your code is about abstractions - take care of those abstractions, nuture them and protect them, they will serve you well.

The question becomes - which one better represents your intent? I would venture to say that the FooImpl is better, I sense your intent is to hide the implementation of the class from the client and this implementation better represents that intent (since FooImpl is not accessible to the client).

If your intent to is to use Bar elsewhere in the code, outside of the class Foo, then that implementation is better because that is the intent and that implementation allows you to do that.

Do these two versions differ in their compilation time?

I doubt that. The implementation of Bar and FooImpl are not visible outside the translation unit they are defined in.

Is one of them more scalable than the other?

Not really, no. In some generic sense, the clearer code is, the easier people are able to scale it.



回答2:

The PIMPL pattern is typically used to completely hide implementation details from code which uses your class.

For example, your class might be wrapping platform-specific functionality. Even if you used forward declarations and/or conditional compilation, you would end up exposing platform-specific code in your header file. This means that any code using your class would end up with a header-dependency on those types as well, and it means your class would potentially change depending on the platform (e.g. the size might be different).

The PIMPL pattern would let you keep all the platform-specific details hidden away in the implementation file (typically *.cpp or similar). This means no other code anywhere else in the program can directly see it, keeping your wrapper class clean and consistent across all platforms.

That's just one example of where PIMPL comes in handy, but there are other uses for it too.

It's also worth noting that not everything can be forward-declared, and forward declarations often require making undesirable concessions, such as using pointers or references everywhere.