如何使所有shared_ptr的副本都等于另一个shared_ptr?

3
我无法理解这个问题..看起来我缺少一些简单的东西? 在(1)点上,我应该在MakePointToSameValue里写什么?
  • 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)

复制指针无效,因为它不会改变c(好吧,c.ptr的引用计数会减少1)。请注意,我只是为了简单起见使用了“int”,但对于不可复制的类型也应该适用。
为什么呢?我有一个表示值(任何类型的值)的类,用于编译器的一种排序。在实例化时,实际值及其存储方式都是未知的。唯一已知的是类型。因此,该类存储包含稍后确定值的占位符的shared_ptr(在作为指针或引用传递函数定义的参数进行编译时相当于:编译器仅知道类型,没有其他信息)。在运行时,应将占位符替换为实际值。
编辑一下,开始新的一天,想到了这个。我知道这很简单。
void MakePointToSameValue( Test& other )
{
  other.ptr.swap( ptr );
  ptr.reset();
  ptr = other.ptr;
}

现在的另一个问题是:上述内容是否适用于任何符合标准的指针?

4
看起来你需要两个间接层。 - Ben Voigt
@CatPlusPlus 请看最后一段。 - stijn
@BenVoigt,我刚刚尝试了一下您所说的内容,它非常简单明了。如果您附带一些伪代码在评论中发布,我将接受它作为答案。 - stijn
2个回答

4
你需要两级间接引用。虽然你说的没错,所有的shared_ptr对象都指向一个共同的元数据块,其中包含计数和指向实际值的指针,但是如果你试图更新该块以指向不同的对象,那么现在就有两个元数据块指向相同的值,每个元数据块都有自己不同的引用计数。 每个元数据块上使用正确数量(意味着它与引用计数匹配)的shared_ptr对象,因此每个块上的计数最终将达到零,但是没有办法知道哪个块是最后一个达到计数为零的块(因此应该删除该值)。 因此,shared_ptr明智地不允许更改元数据内部的对象指针。 您只能将shared_ptr与新的元数据块、新的计数和新的对象相关联。而其他指向同一对象的指针不受影响。
正确的方法是使用第二层间接引用(shared_ptr<shared_ptr<int>>)。 这样每个对象都有恰好一个元数据块和一个计数。 你的更新发生在中间的shared_ptr上。

1

根据我的理解,shared_ptr 并不包含任何这样的机制;也没有任何常规类型。如果您想要这种行为,您需要自己编写代码。我的建议是:添加一个私有静态 std::list<std::weak_ptr<Test>> registry;在构造函数中将每个 Test 实例添加到 registry 列表中进行注册,并确保在析构函数中将其删除。

然后在 MakePointToSameValue 中使用该注册表来遍历所有实例并重置 ptr 的值。

如果您对效率感兴趣并且拥有比三个更多的实例,则需要将 list 替换为 unordered_set;并且在您的 Test 类中可能要使用 unique_ptr 而不是 shared_ptr

对于额外的问题回答:不,它不会起作用。查看reset()的文档:它重置一个特定的实例shared_ptr:它对任何其他实例都没有影响(也不知道)。当控制块中的引用计数达到零时,它还会销毁指针,但仅此而已。


为什么要使用std::list?这太愚蠢了。你可以使用unordered_set来实现O(1)的删除和添加。 - Puppy
没错。这取决于实例的数量,而stijn没有给我们任何线索:如果只有三个,我还是赌列表更有效。否则,当然可以。 - Pontus Gagge
嗯,我以为这应该可以用shared_ptr实现?我的意思是,它包含了所有需要的东西(所有副本在内部都有相同的指针/引用计数对象),只是我暂时无法弄清楚如何使用它。 - stijn
这不是 shared_ptr 的用途,也不是它通常的实现方式。是的,有一个内部管理的共享控制块,一个 shared_ptr 实例通常会有一个指向值的指针,一个指向控制块的指针;控制块不需要有任何指向 shared_ptr 实例的反向指针。你可能会发现一些带有反向指针的实现,这允许你对控制块进行黑客式操作,但你将远离标准 C++。 - Pontus Gagge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接