Search code examples
c++shared-ptrsmart-pointers

How to make all copies of a shared_ptr equal to another shared_ptr?


I cannot figure this out.. Looks like I'm missing something simple? What do I put in MakePointToSameValue so that at point (1)

  • both b.ptr and c.ptr point to the same as a.ptr
  • in other words, a.ptr.get() == b.ptr.get() == c.ptr.get()
  • the value originally pointed to by b.ptr and c.ptr gets deleted

?

struct Test
{
public:
  Test( int val ) :
    ptr( std::make_shared< int >( val ) )
  {
  }

  void MakePointToSameValue( Test& other )
  {
    //what do I put here?
    //other.ptr = this->ptr; //doesn't do it
  }

private:
  std::shared_ptr< int > ptr;
};

Test a( 0 );
Test b( 5 );
Test c( b );

b.MakePointToSameValue( a );

//(1)

Copying the ptr does not work, since it does not alter c (well, c.ptr has it's refcount decreased by one). Note that I use int just for simplicty, but it should work for noncopyable types.

Why? I have a class representing values, any type of values, for use in a sort of compiler. The actual value, nor how it is stored, is known when it gets instantiated. The only thing known is the type. So the class stores a shared_ptr containing a placeholder for a value determined later on (corresponds to compiling the arguments of a function definition when passed as a pointer or reference: the compiler only knows type, nothing more). At runtime the placeholder should be replaced by an actual value.

Edit fresh start of the day, came up with this. I knew it was simple.

void MakePointToSameValue( Test& other )
{
  other.ptr.swap( ptr );
  ptr.reset();
  ptr = other.ptr;
}

Additional question now is: will the above work as expected for any standard compliant pointer?


Solution

  • You need two levels of indirection here. While you're right that all shared_ptr objects point to a common metadata block that contains the count and the pointer to the actual value, if you tried to update that block to point to a different object, you'd now have two metadata blocks pointing to the same value, each with their own different idea of what the reference count is. There's the right number (in the sense that it matches the reference count) of shared_ptr objects using each metadata block, so the count on each block will eventually reach zero, but there's no way to know which block is the last block to reach a count of zero (and hence should delete the value). So shared_ptr sensibly doesn't allow changing the object pointer inside the metadata. You can only associate the shared_ptr with a new metadata block, new count, new object. And other pointers to the same object aren't affected.

    The right way to do this is to use a second layer of indirection (shared_ptr<shared_ptr<int> >). That way there's exactly one metadata block and exactly one count for each object. Your update takes place to the intermediate shared_ptr.