I have a class hierarchy for which I would like to separate the interface from the implementation. My solution is to have two hierarchies: a handle class hierarchy for the interface and a non-public class hierarchy for the implementation. The base handle class has a pointer-to-implementation which the derived handle classes cast to a pointer of the derived type (see function getPimpl()
).
Here's a sketch of my solution for a base class with two derived classes. Is there a better solution?
File "Base.h":
#include <memory>
class Base {
protected:
class Impl;
std::shared_ptr<Impl> pImpl;
Base(Impl* pImpl) : pImpl{pImpl} {};
...
};
class Derived_1 final : public Base {
protected:
class Impl;
inline Derived_1* getPimpl() const noexcept {
return reinterpret_cast<Impl*>(pImpl.get());
}
public:
Derived_1(...);
void func_1(...) const;
...
};
class Derived_2 final : public Base {
protected:
class Impl;
inline Derived_2* getPimpl() const noexcept {
return reinterpret_cast<Impl*>(pImpl.get());
}
public:
Derived_2(...);
void func_2(...) const;
...
};
File "Base.cpp":
class Base::Impl {
public:
Impl(...) {...}
...
};
class Derived_1::Impl final : public Base::Impl {
public:
Impl(...) : Base::Impl(...) {...}
void func_1(...) {...}
...
};
class Derived_2::Impl final : public Base::Impl {
public:
Impl(...) : Base::Impl(...) {...}
void func_2(...) {...}
...
};
Derived_1::Derived_1(...) : Base(new Derived_1::Impl(...)) {...}
Derived_1::func_1(...) const { getPimpl()->func_1(...); }
Derived_2::Derived_2(...) : Base(new Derived_2::Impl(...)) {...}
Derived_2::func_2(...) const { getPimpl()->func_2(...); }
Best Answer
I think it is a poor strategy to make
Derived_1::Impl
derive fromBase::Impl
.The main purpose of using the Pimpl idiom is to hide the implementation details of a class. By letting
Derived_1::Impl
derive fromBase::Impl
, you've defeated that purpose. Now, not only does the implementation ofBase
depend onBase::Impl
, the implementation ofDerived_1
also depends onBase::Impl
.That depends on what trade-offs are acceptable to you.
Solution 1
Make
Impl
classes totally independent. This will imply that there will be two pointers toImpl
classes -- one inBase
and another one inDerived_N
.Solution 2
Expose the classes only as handles. Don't expose the class definitions and implementations at all.
Public header file:
Here's quick implementation
Pros and Cons
With the first approach, you can construct
Derived
classes in the stack. With the second approach, that is not an option.With the first approach, you incur the cost of two dynamic allocations and deallocations for constructing and destructing a
Derived
in the stack. If you construct and destruct aDerived
object from the heap you, incur the cost of one more allocation and deallocation. With the second approach, you only incur the cost of one dynamic allocation and one deallocation for every object.With the first approach, you get have the ability to use
virtual
member function isBase
. With the second approach, that is not an option.My suggestion
I would go with the first solution so I can use the class hierarchy and
virtual
member functions inBase
even though it is a little bit more expensive.