How to make all copies of a shared_ptr equal to an

2019-05-18 15:56发布

问题:

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?

回答1:

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.



回答2:

Well, as far as I understand your requirements, shared_ptr does not contain any such mechanism; nor would any regular type. If you want this behaviour, you'll have to code it yourself. My suggestion: add a private static std::list<std::weak_ptr<Test>> registry; register each Test instance by adding it to the registry list in the constructor, and make sure to remove it in the destructor.

Then use that registry in MakePointToSameValue to iterate through all instances and reset the value of ptr.

If you're interested in efficiency and have a bit more instances than three, you'll want to replace the list with an unordered_set; and perhaps use unique_ptr rather than shared_ptr in your Test class.

Answer to additional question: no, it won't work. Look at the documentation of reset(): it resets one particular instance shared_ptr: it does nothing with (and knows nothing of) any other instances. When the reference count in the control block reaches zero, it additionally destroys the pointee, but that's it.