Search code examples
c++pimpl-idiom

pimpl helper ambiguous with inheritance


I'm playing around with creating a utility class for the pimpl idiom, however I have some problem I hoped to get some help with:

This is what I've got:
[sehe: see also rev.1 here: https://gist.github.com/1379569/9f6cca5703e6195da65e34103393d901dde3b1bf]

pimpl.h

template<typename T>
class pimpl
{
    template <typename> friend class pimpl;

    pimpl(const pimpl& other);
    pimpl(pimpl&& other);
    pimpl& operator=(const pimpl& other);
    pimpl& operator=(pimpl&& other);    
protected:

    ~pimpl();

    struct implementation;
    std::unique_ptr<implementation> impl_;
public:
    pimpl();

    template<typename P0>
    pimpl(P0&& p0);

    pimpl(const T& other);
    pimpl(T&& other);

    void swap(T& other);
};

pimpl_impl.h

// #include "pimpl.h" // TODO add header guards...

template<typename T>
pimpl<T>::pimpl() : impl_(new implementation()){}

template<typename T>
template<typename P0>
pimpl<T>::pimpl(P0&& p0) : impl_(new implementation(std::forward<P0>(p0))){}

template<typename T>
pimpl<T>::~pimpl(){}

template<typename T>
pimpl<T>::pimpl(const T& other) : impl_(new implementation(*((pimpl&)other).impl_)){}

template<typename T>
pimpl<T>::pimpl(T&& other) : impl_(std::move(((pimpl&)other).impl_)){}

template<typename T>
void pimpl<T>::swap(T& other)
{
    impl_.swap(other.impl_);
}

And this is how you use it:

A.h

#include "pimpl.h"

class A : public pimpl<A>
{
    A();
    A(const A& other);
    A(A&& other);
    virtual ~A();
    void foo();
};

A.cpp

#include "A.h"
#include "pimpl_impl.h"

template<>
struct pimpl<A>::implementation
{
     void foo()
     {
     }
};

A::A() : pimpl(){}
A::A(const A& other) : pimpl(other){}
A::A(A&& other) : pimpl(std::move(other)){}
A::~A(){}
void A::foo(){impl_->foo();}

This works quite well, however it quickly breaks down when you put inheritance into the picture:

class B : public pimpl<B>, public A
{
    ...
};

B::B() : pimpl() {} // pimpl ambiguous

pimpl has become ambiguous...

Any ideas as to how to avoid this?


Solution

  • While I wouldn't recommend stuffing pimpl into a curious base class because of Liskov, I don't see any real problem to make it work;

    Like @Nim suggested: it all seems to pan out nicely when doing

    class B : public pimpl<B>, public A
    {
        B() : pimpl<B>() {} 
    };
    

    Gist updated with full sample code that works with B:

    B.h

    #include "A.h"
    
    class B : public pimpl<B>, public A
    {
      public:
        B();
        B(const B& other);
        B(B&& other);
        virtual ~B();
        void bar();
    };
    

    B.cpp

    #include "pimpl_impl.h"
    #include "B.h"
    
    template<>
    struct pimpl<B>::implementation
    {
         void bar()
         {
         }
    };
    
    B::B() : pimpl<B>(){}
    B::B(const B& other) : pimpl<B>(other){}
    B::B(B&& other) : pimpl<B>(std::move(other)){}
    B::~B(){}
    void B::bar(){ pimpl<B>::impl_->bar();}
    

    main.cpp:

    #include "B.h"
    
    int main()
    {
        A a;
        B b;
    }