Can a pimpl variation be implemented without any performance penalty

c++11design-patternsoptimizationperformance

One of the issues of pimpl is the performance penalty of using it (additional memory allocation, non-contiguous data members, additional indirections, etc..). I would like to propose a variation on the pimpl idiom that will avoid these performance penalties at the expense of not getting all the benefits of pimpl. The idea is to leave all private data members in the class itself and move only the private methods to the pimpl class.
The benefit compared to basic pimpl is that the memory remains contiguous (no additional indirection). The benefits compared to not using pimpl at all are:

  1. It hides the private functions.
  2. You can structure it so that all these functions will have internal linkage and allow the compiler to more aggressively optimize it.

So my idea is to make the pimpl inherit from the class itself (sounds a bit crazy I know, but bear with me).
It would look something like this:

In A.h file:

class A
{
    A();
    void DoSomething();
protected:  //All private stuff have to be protected now
    int mData1;
    int mData2;
//Not even a mention of a PImpl in the header file :)
};

In A.cpp file:

#define PCALL (static_cast<PImpl*>(this))

namespace //anonymous - guarantees internal linkage
{
struct PImpl : public A
{
    static_assert(sizeof(PImpl) == sizeof(A), 
                  "Adding data members to PImpl - not allowed!");
    void DoSomething1();
    void DoSomething2();
    //No data members, just functions!
};

void PImpl::DoSomething1()
{
    mData1 = bar(mData2); //No Problem: PImpl sees A's members as it's own
    DoSomething2();
}

void PImpl::DoSomething2()
{
    mData2 = baz();
}

}
A::A(){}

void A::DoSomething()
{
    mData2 = foo();
    PCALL->DoSomething1(); //No additional indirection, everything can be completely inlined
}

As far as I see there are absolutely no performance penalties in using this vs no pimpl and some possible performance gains and cleaner header file interface. One disadvantage this has vs standard pimpl is that you can't hide the data members so changes to those data members will still trigger a recompilation of everything that depends on the header file. But the way I see it, it's either get that benefit or the performance benefit of having the members contiguous in memory (or do this hack – "Why Attempt #3 is Deplorable"). Another caveat is that if A is a templated class the syntax gets annoying (you know, you can't use mData1 directly, you need to do this->mData1, and you need to start using the typename and maybe template keywords for dependent types and templated types, etc.). Yet another caveat is that you can no longer use private in the original class, only protected members, so you can't restrict access from any inheriting class, no just the pimpl. I tried but couldn't get around this issue. For example, I tried making the pimpl a friend template class in the hopes of making the friend declaration broad enough to allow me to define the actual pimpl class in an anonymous namespace, but that just doesn't work. If anyone has any idea of how to keep the data members private and still allow an inheriting pimpl class defined in an anonymous namepsace to access those, I'd really like to see it! That would eliminate my main reservation from using this.

I feel though, that these caveats are acceptable for the benefits of what I propose.

I tried looking online for some reference to this "function-only pimpl" idiom but couldn't find anything. I'm really interested in what people think about this. Are there other issues with this or reasons I shouldn't use this?

UPDATE:

I've found this proposal that more or less tries to accomplish exactly what I am, but doing so by changing the standard. I completely agree with that proposal and hope it will make it into the standard (I know nothing of that process so I have no idea of how likely that's to happen). I'd much rather have it possible to do this through a built in language mechanism. The proposal also explains the benefits of what I'm trying to achieve much better than me. It also doesn't have the issue of breaking encapsulation like my suggestion has (private -> protected). Still, until that proposal makes it into the standard (if that ever happens), I think my suggestion makes it possible to get those benefits, with the caveats I listed.

UPDATE2:

One of the answers mentions LTO as a possible alternative to getting some of the benefits (more aggressive optimizations I'm guessing). I'm not really sure exactly what goes on in various compiler optimization passes but I do have a bit of experience with the resulting code (I use gcc). Simply putting the private methods in the original class will force those to have external linkage.

I might be wrong here, but the way I interpret that is that the compile-time optimizer cannot eliminate the function even if all its call instances are completely inlined inside that TU. For some reason even LTO refuses to get rid of the function definition even if it seems that all the call instances in the entire linked binary are all inlined. I found some references stating that it's because the linker doesn't know if you'll somehow still call the function using function pointers (though I don't understand why the linker can't figure out that the address of that method is never taken).

This is not the case if you use my suggestion and put those private methods in a pimpl inside an anonymous namespace. If those get inlined, the functions will NOT appear in (with -O3, that includes -finline-functions) the object file.

The way I understand it, the optimizer, when deciding whether or not to inline a function, takes into account its impact on code size. So, using my suggestion I'm making it slightly "cheaper" for the optimizer to inline those private methods.

Best Answer

The selling points of the Pimpl pattern are:

  • total encapsulation: there are no (private) data members mentioned in the header file of the interface object.
  • stability: until you break the public interface (which in C++ includes private members), you'll never have to recompile code that depends on the interface object. This makes the Pimpl a great pattern for libraries that don't want their users to recompile all code on every internal change.
  • polymorphism and dependency injection: the implementation or behaviour of the interface object can be easily swapped out at runtime, without requiring dependent code to be recompiled. Great if you need to mock something for an unit test.

To this effect, the classic Pimpl consists of three parts:

  • An interface for the implementation object, which must be public, and use virtual methods for the interface:

    class IFrobnicateImpl
    {
    public:
        virtual int frobnicate(int) const = 0;
    };
    

    This interface is required to be stable.

  • An interface object that proxies to the private implementation. It does not have to use virtual methods. The only allowed member is a pointer to the implementation:

    class Frobnicate
    {
        std::unique_ptr<IFrobnicateImpl> _impl;
    public:
        explicit Frobnicate(std::unique_ptr<IFrobnicateImpl>&& impl = nullptr);
        int frobnicate(int x) const { return _impl->frobnicate(x); }
    };
    
    ...
    
    Frobnicate::Frobnicate(std::unique_ptr<IFrobnicateImpl>&& impl /* = nullptr */)
    : _impl(std::move(impl))
    {
        if (!_impl)
            _impl = std::make_unique<DefaultImplementation>();
    }
    

    The header file of this class must be stable.

  • At least one implementation

The Pimpl then buys us a great deal of stability for a library class, at the cost of one heap allocation and additional virtual dispatch.

How does your solution measure up?

  • It does away with encapsulation. Since your members are protected, any subclass can mess with them.
  • It does away with interface stability. Whenever you change your data members – and that change is just one refactoring away – you'll have to recompile all dependent code.
  • It does away with the virtual dispatch layer, preventing easy swapping of the implementation.

So for every objective of the Pimpl pattern, you fail to fulfil this objective. It is therefore not reasonable to call your pattern a variation of the Pimpl, it is much more an ordinary class. Actually, it's worse than an ordinary class because your member variables are private. And because of that cast which is a glaring point of fragility.

Note that the Pimpl pattern is not always optimal – there's a tradeoff between stability and polymorphism on the one hand, and memory compactness on the other. It is semantically impossible for a language to have both (without JIT compilation). So if you're micro-optimizing for memory compactness, clearly the Pimpl is not a suitable solution for your use case. You'll also probably stop using half the standard library, since these awful string and vector classes involve dynamic memory allocations ;-)