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:
-
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. -
Well understood:
The interface technology is better understood by every C++ developer.
-
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?
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:
-
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
-
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).
-
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:
-
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.
-
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.
-
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
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
This page answers your question. Many people agree with your assertion. Using Pimpl has the following advantages over private fields/members.
- 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.- 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