49

I see a lot of source code which using 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 not worse than PImpl idiom, both 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?

50

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.

  • Good point. Add public function in Impl class will not break the ABI, but add public function in interface will break ABI. – ZijingWu Oct 3 '13 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.) – Donal Fellows May 4 '14 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 May 4 '14 at 15:43
  • 4
    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 Mar 18 '16 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... – Martin C. Martin May 18 '17 at 13:54
7

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.

  • 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 Nov 7 '13 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. – Martin C. Martin May 18 '17 at 13:56
5

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?

  • 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 Oct 3 '13 at 11:19
  • @JanHudec clarified. – Dave Hillier Oct 3 '13 at 11:20
  • @DaveHillier, +1 for mention FragileBinaryInterfaceProblem – ZijingWu Oct 3 '13 at 13:09

protected by gnat Jan 11 '17 at 20:50

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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