57

I see a lot of source code that uses PImpl idiom in C++. I assume Its purpose is to hide the private data/type/implementation, so it can remove dependence, and then reduce compile time and header include issue.

But interface/pure-abstract classes in C++ also have this capability, they can also be used to hide data/type/implementation. And to let the caller just see the interface when creating an object, we can declare a factory method in the interface's header.

The comparison is:

  1. Cost:

    The interface way cost is lower, because you don't even need to repeat the public wrapper function implementation void Bar::doWork() { return m_impl->doWork(); }, you just need to define the signature in the interface.

  2. Well understood:

    The interface technology is better understood by every C++ developer.

  3. Performance:

    Interface way performance is not worse than PImpl idiom, both requires an extra memory access. I assume the performance is same.

Following is the pseudocode to illustrate my question:

// Forward declaration can help you avoid include BarImpl header, and those included in BarImpl header.
class BarImpl;
class Bar
{
public:
    // public functions
    void doWork();
private:
    // You don't need to compile Bar.cpp after changing the implementation in BarImpl.cpp
    BarImpl* m_impl;
};

The same purpose can be implemented using interface:

// Bar.h
class IBar
{
public:
    virtual ~IBar(){}
    // public functions
    virtual void doWork() = 0;
};

// to only expose the interface instead of class name to caller
IBar* createObject();

So what's the point of PImpl?

3 Answers 3

60

First, PImpl is usually used for non-polymorphic classes. And when a polymorphic class has PImpl, it usually remains polymorphic, that is still implements interfaces and overrides virtual methods from base class and so on. So simpler implementation of PImpl is not interface, it is a simple class directly containing the members!

There are three reasons to use PImpl:

  1. Making the binary interface (ABI) independent of the private members. It is possible to update a shared library without recompiling the dependent code, but only as long as the binary interface remains the same. Now almost any change in header, except for adding a non-member function and adding a non-virtual member function, changes the ABI. The PImpl idiom moves definition of the private members into the source and thus decouples the ABI from their definition. See Fragile Binary Interface Problem

  2. When a header changes, all sources including it have to be recompiled. And C++ compilation is rather slow. So by moving definitions of the private members into the source, the PImpl idiom reduces the compilation time, as fewer dependencies need to be pulled in the header, and reduces the compilation time after modifications even more as the dependents don't need to be recompiled (ok, this applies to interface+factory function with hidden concrete class too).

  3. For many classes in C++ exception safety is an important property. Often you need to compose several classes in one so that if during operation on more than one member throws, none of the members is modified or you have operation that will leave the member in inconsistent state if it throws and you need the containing object to remain consistent. In such case you implement the operation by creating new instance of the PImpl and swap them when the operation succeeds.

Actually interface can also be used for implementation hiding only, but has following disadvantages:

  1. Adding non-virtual method does not break ABI, but adding a virtual one does. Interfaces therefore don't allow adding methods at all, PImpl does.

  2. Inteface can only be used via pointer/reference, so the user has to take care of proper resource management. On the other hand classes using PImpl are still value types and handle the resources internally.

  3. Hidden implementation can't be inherited, class with PImpl can.

And of course interface won't help with exception safety. You need the indirection inside the class for that.

10
  • Good point. Add public function in Impl class will not break the ABI, but add public function in interface will break ABI.
    – ZijingWu
    Commented Oct 3, 2013 at 12:27
  • 1
    Adding a public function/method doesn't have to break the ABI; strictly additive changes aren't breaking changes. However, you have to be ever so careful; the code mediating between the front-end and the back-end has to deal with all sorts of fun with versioning. It all gets tricky. (This sort of thing is why a number of software projects prefer C to C++; it lets them get a tighter grip on exactly what the ABI is.) Commented May 4, 2014 at 10:18
  • 3
    @DonalFellows: Adding a public function/method does not break ABI. Adding a virtual method does and does so always unless the user is prevented from inheriting the object (which PImpl achieves).
    – Jan Hudec
    Commented May 4, 2014 at 15:43
  • 6
    To clarify why adding a virtual method breaks the ABI. It has to do with linkage. Linking to non-virtual methods is by name, regardless whether the library is static or dynamic. Adding another non-virtual method is just adding another name to link to. However virtual methods are indices in a vtable, and external code effectively links by index. Adding a virtual method can move other methods around in the vtable without any way for the external code to know.
    – SnakE
    Commented Mar 18, 2016 at 13:13
  • 1
    PImpl also reduces the initial compile time. For example, if my implementation has a field of some template type (e.g. unordered_set), without PImpl, I need to #include <unordered_set> in MyClass.h. With PImpl, I only need to include it in the .cpp file, not the .h file, and therefore everything else that includes it... Commented May 18, 2017 at 13:54
9

I just want to address your performance point. If you use an interface, you necessarily have created virtual functions which WILL NOT be inlined by the compiler's optimizer. The PIMPL functions can (and probably will, because they are so short) be inlined (perhaps more than once if the IMPL function is also small). A virtual function call can't be optimized over unless you use full program analysis optimizations which take a very long time to do.

If your PIMPL class is not used in a performance critical way, then this point doesn't matter much, but your assumption that the performance is the same only holds in some situations, not all.

4
  • 1
    With the use of link-time optimization, most of the overhead of using pimpl idiom is removed. Both the outter wrapper functions as well as the implementation functions can be inlined, making the function calls effectively like a normal class implemented inside a header.
    – goji
    Commented Nov 7, 2013 at 4:17
  • This is only true if you use link-time optimization. The point of PImpl is that the compiler doesn't have the implementation, not even of the forwarding function. The linker does though. Commented May 18, 2017 at 13:56
  • Isn't runtime cost of pointer dereference similar to that of virtual function call? I don't really understand what kind of link time optimization could eliminate that.
    – hamilyon
    Commented Jan 26, 2021 at 8:04
  • @hamilyon> you need one more indirection using inheritance, and on cache miss the cpu has a harder time doing useful stuff while the cache line gets loaded, since it does not even know what code it will be running.
    – spectras
    Commented Apr 11, 2021 at 23:27
7

This page answers your question. Many people agree with your assertion. Using Pimpl has the following advantages over private fields/members.

  1. Changing private member variables of a class does not require recompiling classes that depend on it, thus make times are faster, and the FragileBinaryInterfaceProblem is reduced.
  2. The header file does not need to #include classes that are used 'by value' in private member variables, thus compile times are faster.

The Pimpl idiom is a compile-time optimisation, a dependency breaking technique. It cuts down large header files. It reduces your header to only the public interface. Header length is important in C++ when you think about how it works. #include effectively concatenates the header file to the source file - so bear in mind after pre-processed C++ units can be very large. Using Pimpl can improve compile times.

There are other better dependency breaking techniques - that involve improving your design. What do the private methods represent? What is the single responsibility? Should they be another class?

6
  • PImpl is not an optimization in run-time respect at all. Allocations are not trivial and pimpl means extra allocation. It is a dependency-breaking technique and optimization of compile time only.
    – Jan Hudec
    Commented Oct 3, 2013 at 11:19
  • @JanHudec clarified. Commented Oct 3, 2013 at 11:20
  • @DaveHillier, +1 for mention FragileBinaryInterfaceProblem
    – ZijingWu
    Commented Oct 3, 2013 at 13:09
  • @JanHudec Note that pimpl does not necessarily imply extra allocation, as the external class can have value semantics (possibly move-only).
    – spectras
    Commented Apr 11, 2021 at 23:29
  • @spectras, extra allocation in addition to however the client code allocates the external class, compared to including the internal members directly. Yes, there are cases where the external class can have value semantics while the internal class would have to be wrapped in a unique_ptr or a shared_ptr anyway, so you are just wrapping that, but I'd say it's fairly rare it would *have to*—the client code is usually fairly free to choose where it places objects.
    – Jan Hudec
    Commented Apr 12, 2021 at 8:30

Not the answer you're looking for? Browse other questions tagged or ask your own question.