Here's a toy example illustrating a problem I encounter. The application is fairly irrelevant (it's essentially a linked list of elements with a special behavior at the end). I'm unable to construct a base class shared_ptr with a derived pointer and it's for some reason linked to the fact that I'm using private inheritance.
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
using namespace std;
// An Item in a linked list
class A
{
public:
//friend class B;
typedef boost::shared_ptr<A> APtr;
A() : next_() {}
A(APtr n) : next_(n) {}
APtr next() { return next_; }
void setNext(APtr n) { next_ = n; }
virtual void doIt() { /* standard behavior */ }
private:
APtr next_;
};
class B : private A // B really is a special A
// that should have different behavior
// at the tail of the chain
// but I want to hide A's interface
// to external clients
{
public:
typedef boost::shared_ptr<B> BPtr;
B(A::APtr prev)
{ // Set this object as the tail
prev->setNext(APtr(this)); /* WHY CAN'T I CONSTRUCT APtr(this)
WITH PRIVATE INH. */
}
void doIt() {/*special behavior at end */}
};
int main()
{
A::APtr dummyPtr;
A::APtr head = boost::make_shared<A>(dummyPtr);
B::BPtr tail = boost::make_shared<B>(head);
for(A::APtr curr = head; curr; curr=curr->next()){
curr->doIt();
}
return 0;
}
and I get this
/usr/include/boost/smart_ptr/shared_ptr.hpp: In constructor ‘boost::shared_ptr<T>::shared_ptr(Y*) [with Y = B, T = A]’:
derived_shared.cpp:31: instantiated from here
/usr/include/boost/smart_ptr/shared_ptr.hpp:352: error: ‘A’ is an inaccessible base of ‘B’
I was under the impression that private inheritance allows the Derived class to still access the public interface of the base class but hides that interface to external clients. Why does private inheritance cause this error (it works if I inherit publicly)?
Change this single line:
to
And it compiles.
Or at least it does when using the
std
library. It is usually similar forboost
.There are other errors but that gets round casting
B*
toA*
.Why does that work? Because the template for the constructor
std::shared_ptr<A>
isn't what you think it is! It is more liketemplate <class X> std::shared_ptr(X* v)
. So the actualB*
toA*
cast is postponed and failing in a non-friend member.But if you cast the
B*
pointer (i.e.this
) toA*
inside a method ofclass B
(the only place that is legal without afriend
declaration) you're in.NB: There is nothing in principle wrong with private inheritance. It isn't an anti-pattern and is provided for good reason. Do think about composition but objects that forbid some parts of the application from 'accessing' their 'real' type have lots of uses. For example pass out an object A that has some B bolt ons that only the object factory can access.
PS: The reason why the constructor is
template<class T> shared_ptr<T* v>
is so thatshared_ptr
uses the deleter of the type passed into it. As you're not doubt awareshare_ptr
cleverly calls the 'right' destructor even if it isn't virtual. My 'fix' actually subverts that cleverness so beware to pass in the right deleter or (recommended) make the destructor ofA
virtual.PPS:
And finally a fully working program (using STL. Sorry I don't have Boost):
You need to use
enable_shared_from_this
because otherwise you're trying to create two 'families' ofshared_ptr
and that won't work.I've made a factory method because fixing the constructor just wouldn't work! There is a precondition of
enable_shared_from_this
that there has to be astd::shared_ptr
in existence and I suppose that means 'fully constructed'.The following constructor will not work for me:
That said, if you do inherit from
enable_shared_from_this
it's a good idea to make all the constructors private and provide factories that returnshared_ptr
. Otherwise you can get in a right mess if calling code doesn't itself ensure that 'pre-existingshared_ptr
' condition. A nasty piece of coupling if ever there was one.