4

I am looking at the shared_ptr implementation in the following post. One question that is not entirely clear to me is, why in addition to the pointer stored with T* type in shared_ptr class itself, author also needs to store the second copy of the managed object's pointer with its concrete type in the control block (i.e. U* p; in auximpl). I understand why we need this for custom deleter, but not for actual pointer. It looks like this complies with the requirements of the standard, when I read control block description in cppreference page. Author made the following comment about this

"this is needed to properly manage all the cases of T being a base for whatever U having multiple T in the derivation hierarchy"

but I still can't come up with an example of when this will really be required. Can someone please demonstrate it with an example?

Thank you, -Grigor

  • Just at an example: `class A{public: virtual ~A(){}}; class BA: public A{}; class CA: public A{}; class D: public BA, public CA{}; std::shared_ptr p(new D);`. Not knowing about `D*` the inner `delete p.ptr` will not work as expected. – 273K Jul 10 '22 at 19:14
  • Doesn't that give `error: 'A' is an ambiguous base of 'D'`? – Goswin von Brederlow Jul 10 '22 at 19:53
  • https://godbolt.org/z/PavfvcaoY Lots of other errors that I believe are all due to D being ambiguous. But once you dynamic_cast to `BA` or `CA` it works. And then the virtual destructor A::~A() will do the right thing. So how is the inner pointer needed? I would agree with the Op that the inner pointer is for custom deleters. – Goswin von Brederlow Jul 10 '22 at 20:06
  • If I understand 273K example correctly he needed to derive from A virtually in BA and CA. This will fix the compilation errors, however I still don't get why would we want to store the second instance of pointer in the control block. We could just hold it as T* in shared_ptr itself (not in the control block) and pass it to destroy function as a parameter to call custom deleter on it. Thus the auximpl struct should have the following prototype in my opinion (see below comment) – Grigor Aleksanyan Jul 10 '22 at 20:56
  • template class shared_ptr { … template struct auximpl: public aux { Deleter d; auximpl(Deleter x) : d(x) {} void destroy(T* ptr) override { d(p); } }; … } – Grigor Aleksanyan Jul 10 '22 at 20:56
  • The managed object pointer may not be in the shared_ptr. It has to have a pointer to something and a pointer to the control block. Destruction needs the pointer to the managed object though. – Jeff Garrett Jul 10 '22 at 23:18

1 Answers1

3

One case is when using shared_ptr's alias capability.

Create a shared_ptr using an object's member but the control block is the same and thus the reference count.

class holder : public std::enable_shared_from_this<holder>
{
 int member;

public:
 
 std::shared_ptr<int> get_member() { 
  return std::shared_ptr<int>(shared_from_this(), &member);
 }
};

std::shared_ptr<int> foo()
{
 auto ptr = std::make_shared<holder>();
 return ptr->get_member();
}

the object created in the make_shared call won't be freed until the object returned from foo's reference count goes to 0.

bolov
  • 72,283
  • 15
  • 145
  • 224
SoronelHaetir
  • 14,104
  • 1
  • 12
  • 23