Please see my implementation of PIMPL with inheritance. In derived class, DerivedImpl inherits from BaseImpl.
Question: Should the pointer to Impl only defined in base class like the following code? If so, every time I need to use the base pointer, I have to cast it to derived type. However, static cast a shared_ptr looks expensive according to profiling result because this cast is extensively used. And the cast function can't be inline in header because it is incomplete there.
Maybe I made some mistakes. Or is there better implementation for this using smart pointer?
// Base.h
class BaseImpl; // pre-declaration
class Base
{
public:
Base();
explicit Base(BaseImpl* ptr);
~Base();
protected:
std::shared_ptr<BaseImpl> d_Ptr;
};
// baseimpl.h
class BaseImpl
{
double mDate;
};
// Derived.h
#include "Base.h"
class DerivedImpl;
class Derived :
public Base
{
public:
Derived();
~Derived();
std::shared_ptr<DerivedImpl> d_func();
const std::shared_ptr<DerivedImpl> d_func() const;
};
// Derived.cpp
#include "Derived.h"
#include "DerivedImpl.h"
Derived::Derived() : Base(new DerivedImpl())
{
}
Derived::~Derived()
{
}
std::shared_ptr<DerivedImpl> Derived::d_func()
{
return std::static_pointer_cast<DerivedImpl>(d_Ptr);
}
const std::shared_ptr<DerivedImpl> Derived::d_func() const
{
return std::static_pointer_cast<DerivedImpl>(d_Ptr);
}
I'll assume that you want exactly what you describe, modulo implementation details:
An inheritance hierarchy of public classes.
Based on a corresponding inheritance hierarchy of implementation classes.
The implementation's possible use of the global namespace and/or macros, should be confined to separately compiled units.
This is a problem, derived class specific initialization, that pops up e.g. when wrapping a set of low level GUI widgets in C++ classes. And also in many other situations. There is a wide range of possible solutions, but your so-far solution is to pass a pointer to implementation up through the base class constructors, to the top-most base, where it is provided to derived classes.
Still, you're not sure that that's a good idea:
” Should the pointer to Impl only defined in base class like the following code?
Yes, ideally it should, because this approach ensures that an available base class instance is always fully constructed. And that's the basic idea of C++ constructors. That after initialization (e.g. of a base class sub-object), you either have a working object at hand, or nothing, i.e. exception or termination.
However, this approach serves you back two problems:
How to efficiently provide derived class implementation pointer?
How to at all derive an implementation from a base class implementation?
The latter problem is easily solved by having separate header files for the implementation. Keeping in mind that the point of information hiding is not to make these classes' source code physically inaccessible, although that's still possible. But to avoid pollution of the global namespace, and macro-land.
The first problem, which is what you actually ask about,
” static cast a shared_ptr looks expensive according to profiling result because this cast is extensively used
is not really a problem.
The downcast functions need only be accessible in the implementation part of the code, and there their source code is available and the calls can be inlined.
Finally, just advice, you should use unique_ptr
, or no smart pointer, or perhaps an automatically cloning smart pointer, rather than shared_ptr
, for the implementation pointer. Because you generally don't want a copy of an instance of your public class, to share its implementation with the original instance. Other than for the case where the implementation has no state, in which case it's not very meaningful to allocate it dynamically.
Example:
Base.hpp:#pragma once
#include <memory>
namespace my {
using std::unique_ptr;
class Base
{
protected:
class Impl;
private:
unique_ptr<Impl> p_impl_;
protected:
auto p_impl() -> Impl* { return p_impl_.get(); }
auto p_impl() const -> Impl const* { return p_impl_.get(); }
Base( unique_ptr<Impl> p_derived_impl );
public:
auto foo() const -> char const*;
~Base();
Base();
};
} // namespace my
Base.Impl.hpp:
#pragma once
#include "Base.hpp"
class my::Base::Impl
{
public:
auto virtual foo() const -> char const* { return "Base"; }
virtual ~Impl() {}
};
Base.cpp:
#include "Base.Impl.hpp"
#include <utility> // std::move
using std::move;
using std::unique_ptr;
auto my::Base::foo() const
-> char const*
{ return p_impl()->foo(); }
my::Base::~Base() {}
my::Base::Base()
: p_impl_( new Impl() )
{}
my::Base::Base( unique_ptr<Impl> p_derived_impl )
: p_impl_( move( p_derived_impl ) )
{}
Derived.hpp:
#pragma once
#include "Base.hpp"
namespace my {
class Derived
: public Base
{
protected:
class Impl;
Derived( unique_ptr<Impl> p_morederived_impl );
private:
auto p_impl() -> Impl*;
auto p_impl() const -> Impl const*;
public:
~Derived();
Derived();
};
} // namespace my
Derived.Impl.hpp:
#pragma once
#include "Base.Impl.hpp"
#include "Derived.hpp"
class my::Derived::Impl
: public my::Base::Impl
{
public:
auto foo() const -> char const* override { return "Derived"; }
};
Derived.cpp:
#include "Derived.Impl.hpp"
#include <utility> // std::move
using std::move;
using std::unique_ptr;
inline auto my::Derived::p_impl() -> Impl*
{ return static_cast<Impl*>( Base::p_impl() ); }
inline auto my::Derived::p_impl() const -> Impl const*
{ return static_cast<Impl const*>( Base::p_impl() ); }
my::Derived::~Derived() {}
my::Derived::Derived()
: Base( unique_ptr<Impl>( new Impl() ) )
{}
my::Derived::Derived( unique_ptr<Impl> p_morederived_impl )
: Base( move( p_morederived_impl ) )
{}
main.cpp:
#include "Derived.hpp"
#include <iostream>
using namespace std;
auto main() -> int
{
wcout << my::Derived().foo() << endl;
}
Technicality: In class Derived
the downcaster functions are private
, to prevent them being used directly by a yet more derived class. That's because the implementations are inline
, and should be defined identically in every translation unit where they're used. Rather than dividing this up into yet more headers, the more derived class should/can cast directly down from the Base
implementation, just like Derived
does.