Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

shared_ptr and private inheritance

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)?

like image 200
innocent_bystander Avatar asked Jun 10 '15 18:06

innocent_bystander


2 Answers

Change this single line:

prev->setNext(APtr(this)); 

to

prev->setNext(APtr(static_cast<A*>(this))); 

And it compiles.

Or at least it does when using the std library. It is usually similar for boost.

There are other errors but that gets round casting B* to A*.

Why does that work? Because the template for the constructor std::shared_ptr<A> isn't what you think it is! It is more like template <class X> std::shared_ptr(X* v). So the actual B* to A* cast is postponed and failing in a non-friend member.

But if you cast the B* pointer (i.e. this) to A* inside a method of class B (the only place that is legal without a friend 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 that shared_ptr uses the deleter of the type passed into it. As you're not doubt aware share_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 of A virtual.

PPS:

And finally a fully working program (using STL. Sorry I don't have Boost):

#include <iostream>
#include <memory>

// An Item in a linked list
class A
{
public:
  //friend class B;
  typedef std::shared_ptr<A> APtr;
  A() : next_() {}
  A(APtr n) : next_(n) {}
  APtr next() { return next_; }
  void setNext(APtr n) { next_ = n;}

  virtual void doIt() { std::cout<<"normal thing"<<std::endl; }  

  virtual ~A(){}
private:
  APtr next_;
};

class B : public std::enable_shared_from_this<A>, 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:
  template<class X> friend class std::enable_shared_from_this;

  typedef std::shared_ptr<B> BPtr;


  static BPtr makeit(A::APtr prev){
    BPtr B(std::make_shared<B>());
    prev->setNext(B->shared_from_this());
    return B;
  } 

  void doIt() {std::cout<<"end thing"<<std::endl;}
private: 
  B(){}
};

int main()
{
  A::APtr dummyPtr;
  A::APtr head = std::make_shared<A>(dummyPtr);
  B::BPtr tail = B::makeit(head);

  for(A::APtr curr = head; curr; curr=curr->next()){
    curr->doIt();
  }

  return 0;
}

You need to use enable_shared_from_this because otherwise you're trying to create two 'families' of shared_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 a std::shared_ptr in existence and I suppose that means 'fully constructed'.

The following constructor will not work for me:

B(A::APtr prev){
   prev->setNext(shared_from_this());
} 

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 return shared_ptr. Otherwise you can get in a right mess if calling code doesn't itself ensure that 'pre-existing shared_ptr' condition. A nasty piece of coupling if ever there was one.

like image 172
Persixty Avatar answered Oct 26 '22 10:10

Persixty


When you use private inheritance, you're basically saying "I want B to be implemented in terms of A, but I don't want it to be used like a A (is-a A)"

Here, you're giving boost::shared_ptr a pointer to B as if it was an A.

That's a contradiction in your design. Maybe declaring boost::shared_ptr<A> a friend of B would help, but it's still a weird design.

Additional note: If you want B to be a special A that doesn't expose A's interface, consider composition instead of private inheritance

like image 39
KABoissonneault Avatar answered Oct 26 '22 11:10

KABoissonneault