0

The following header and implementation files combines all three concepts but won't compile:

$ cat a.h
#include <memory>

class Base {
protected:
    class BaseImpl;
    std::shared_ptr<BaseImpl> pImpl;
    Base(BaseImpl* impl)
        : pImpl{impl}
    {}
public:
    virtual ~Base()
    {}
    virtual void func() =0;
};

class Der : public virtual Base {
private:
    class DerImpl;
    DerImpl* getPimpl() const noexcept;
public:
    Der();
    virtual ~Der()
    {}
    void func();
};
$ cat a.cpp
#include "a.h"

class Base::BaseImpl {
};

class Der::DerImpl : public virtual Base::BaseImpl {
public:
    void func() {}
};  

Der::Der()
    : Base{new DerImpl()}
{}  

Der::DerImpl* Der::getPimpl() const noexcept {
    return static_cast<DerImpl*>(pImpl.get());
}

void Der::func() {
    getPimpl()->func();
}
$ g++ --std=c++11 -c a.cpp
a.cpp: In member function ‘Der::DerImpl* Der::getPimpl() const’:
a.cpp:16:45: error: cannot convert from base ‘Base::BaseImpl’ to derived type ‘Der::DerImpl’ via virtual base ‘Base::BaseImpl’
     return static_cast<DerImpl*>(pImpl.get());
                                             ^

Would you please tell me what's wrong, why, and how I might have separate declaration and definition files with the Pimpl idiom and multiple virtual inheritance.

Steve Emmerson
  • 7,702
  • 5
  • 33
  • 59

3 Answers3

0

The multiple virtual base classes question and pimpl idioms are completely different topics. pimpl and impl's are typically in separate headers and cpp files. The whole pimpl idiom is a pointer to implementation, meaning that the pimple wrapps the impl's functions to expose them to the client and contains only a void pointer to the impl that's cast to the actual impl in the pimpl's cpp file, and creates and destroys this in it's constructors and deconstructors. You can also implement more functionality if you wish to but this type of idiom is typically used to prevent intellectual property from being exposed in the header files shipped with a binary dll. It can also solidify your interface so that you don't accidentally break clients applications in future releases of your product.

It's for library sharing, not so much production code of a single application. As far as base classes and inheritance, your base class has a pointer to an instance of it's self. Lets focus on one thing at a time. This should serve to help explain the pimpl idiom in detail for you. If you need multiple virtual inheritance for your product and it's a library just expose it using the pimpl idiom if you wish. If you need a refresher on multiple inheritance you can find it here.

johnathan
  • 2,315
  • 13
  • 20
0

compiler error tells you cannot static_cast from derived type to base type. Use either dynamic_cast, or reinterpret_cast (reinterpret_cast is faster, but does not guarantee to work properly with multiple inheritance).

p.s. Also, don't forget virtual destructors for virtual base classes.

Andrei R.
  • 2,374
  • 1
  • 13
  • 27
0

I managed to combine the concepts (pImpl with separate files and multiple virtual inheritance) by

  • Saving a pointer to void in pImpl rather than a pointer to an implementation to avoid modification of the pointer by the base constructor; and
  • Using reinterpret_cast rather than static_cast in getPimpl() in the derived classes to work-around the inability to downcast in a virtual inheritance environment.

Here's the code and a test routine:

$ cat a.h
#include <memory>

class Base {
protected:
    class Impl;
    std::shared_ptr<void> pImpl;
    Base(void* impl) : pImpl{impl} {}
public:
    virtual ~Base() =0; 
};

class Der1 : public virtual Base {
protected:
    class Impl; // Won't compile if `private`
private:
    Impl* getPimpl() const noexcept;
public:
    Der1();
    virtual ~Der1() {}
    void der1Func();
};

class Der2 : public virtual Base {
protected:
    class Impl; // Won't compile if `private`
private:
    Impl* getPimpl() const noexcept;
public:
    Der2();
    virtual ~Der2() {}
    void der2Func();
};

class Joined : public Der1, public Der2 {
private:
    class Impl;
    Impl* getPimpl() const noexcept;
public:
    Joined();
    void joinedFunc();
};
$ cat a.cpp
#include "a.h"
#include <iostream>

class Base::Impl {};

class Der1::Impl : public virtual Base::Impl {
public:
    void der1Func() {
        std::cout << "Der1::Impl::der1Func() called\n";
    }   
};

class Der2::Impl : public virtual Base::Impl {
public:
    void der2Func() {
        std::cout << "Der2::Impl::der2Func() called\n";
    }   
};

class Joined::Impl : public virtual Der1::Impl, public virtual Der2::Impl {
public:
    void joinedFunc() {
        std::cout << "Joined::Impl::joinedFunc() called\n";
    }   
};

Base::~Base() {
    reinterpret_cast<Impl*>(pImpl.get())->~Impl();
}

Der1::Der1() : Base{new Impl()} {}

Der1::Impl* Der1::getPimpl() const noexcept {
    return reinterpret_cast<Impl*>(pImpl.get());
}

void Der1::der1Func() {
    getPimpl()->der1Func();
}

Der2::Der2() : Base{new Impl()} {}

Der2::Impl* Der2::getPimpl() const noexcept {
    return reinterpret_cast<Impl*>(pImpl.get());
}

void Der2::der2Func() {
    getPimpl()->der2Func();
}

Joined::Joined() : Base{new Impl()} {}

Joined::Impl* Joined::getPimpl() const noexcept {
    return reinterpret_cast<Impl*>(pImpl.get());
}

void Joined::joinedFunc() {
    getPimpl()->joinedFunc();
}

int main() {
    Joined* joined = new Joined();
    joined->joinedFunc(); // Calls Der1::joinedFunc()
    joined->der1Func();   // Calls Der1::der1Func()
    joined->der2Func();   // Calls Der2::der2Func()
    Der1*   der1 = joined;
    der1->der1Func();     // Calls Der1::der1Func()
}
$ g++ --std=c++11 a.cpp && ./a.out
Joined::Impl::joinedFunc() called
Der1::Impl::der1Func() called
Der2::Impl::der2Func() called
Der1::Impl::der1Func() called
$
Steve Emmerson
  • 7,702
  • 5
  • 33
  • 59