std::set 次 shared_ptr 次擦除导致 SIGABRT

std::set of shared_ptr's erasing leads to SIGABRT

我有 std::set 个指向某种类型对象的共享指针(这里的 int 只是为了举例)。我需要的是插入由原始指针构造的共享指针。但是当我试图擦除一些集合元素时(同样我只有一个原始指针),我应该构造 shared_ptr 并将它传递给擦除方法(在我看来这是真的,因为 shared_ptr的比较运算符在 ) .

中比较它们的原始指针

代码片段,导致 SIGABRT:

std::set<std::shared_ptr<int>> sett;
int *rp = new int(5);
sett.emplace( rp );
sett.erase( std::shared_ptr<int>( rp ) );

这不行:

sett.erase( shared_ptr<int>( rp ) );

这里,rp是一个指针,所以你构造一个匿名临时shared_ptr,然后你删除它指向的值和内存,然后你的匿名临时再删除它。

您不得构造两个指向同一对象的不同 shared_ptr。如果您需要这些方面的东西,您可以考虑 enable_shared_from_this。或者更好的是,通过为 std::set 实现允许与原始指针进行比较的比较函数,从容器中擦除而根本不构造 shared_ptr 。有关更多信息,请参阅:What are transparent comparators?

引用自cppreference

Constructing a new shared_ptr using the raw underlying pointer owned by another shared_ptr leads to undefined behavior.

当您这样做时:

sett.emplace( rp );

由于 implicit type conversion 创建了 shared_ptr 并赋予了 rp 指向的内存位置的所有权。让我们称之为 sp1(1) 其中 (1) 表示引用计数

当你这样调用时: sett.erase( std::shared_ptr<int>( rp ) );

发生以下事件序列:

  • 一个新的 shared_ptr 拥有 rp 指向的内存。 shared_ptr认为内存的引用计数为1。sp2(1)
  • erase 被调用。这将调用 default comparator。这可能会导致删除第一个 shared_ptr sp1

  • sp1 调用析构函数,引用计数将变为 0。由于引用计数为 0,因此内存被释放。

  • When erase returns sp2(1) 析构函数被调用(因为这是为函数调用临时创建的)。对于由 sp2

  • 管理的内存,引用计数变为 0
  • 析构函数不知道底层内存的命运已经被删除 sp1 再次调用内存上的 delete。 Double delete disaster 发生在此时。

这就是为什么,要么从创建内存时就shared_ptr 保留所有内容,要么为您的指针类型编写自定义比较器。