共享指针递归删除递归数据结构,堆栈溢出

Posted

技术标签:

【中文标题】共享指针递归删除递归数据结构,堆栈溢出【英文标题】:Shared pointers delete recursive data structures recursively and the stack overflows 【发布时间】:2013-07-22 04:06:46 【问题描述】:

我有许多长链表(它们最多有 20,000 个项目)。它们有不同的起点,但它们最终可以从某个节点开始指向同一个节点。我决定让这样的链表一起成长,共享它们之间的记忆。

这就是我决定用共享指针实现链表的原因:

#include <memory>
struct SharedLinkedList 
    int someData;
    std::shared_ptr<SharedLinkedList> next;
;

这样一切正常。不再需要的链表被删除。如果他们与其他链表共享某些部分,则仅删除其未共享的部分。

当没有共享部分的较长链表即将被删除时,就会出现问题。删除从第一个元素开始。这减少了对下一个也可以删除的元素的引用数量,并且递归重复直到堆栈溢出。

这是创建长链表然后删除它失败的代码示例。

SharedLinkedList* beginningOfList;
SharedLinkedList* actualElement = new SharedLinkedList();
SharedLinkedList* nextElement;

beginningOfList = actualElement;
for (int i = 0; i < 1000; i++)  // 100 is OK, 1000 is KO
    nextElement = new SharedLinkedList();
    actualElement->next = std::shared_ptr<SharedLinkedList>(nextElement);
    actualElement = nextElement;

delete beginningOfList;

我提前感谢以下任何一项:

    shared_pointers 的解释以及我缺少什么。我该如何使用它们?甚至可以使用它们来完成吗?这种内存共享难道不是发明共享指针的目的吗? 建议如何重新实现我的代码 此代码将用于在我的计算机上运行的科学计算。我可以通过某种方式调整一些东西以获得更大的堆栈吗?

请注意,这个问题不是 c++11 特定的。我不在乎使用了共享指针的哪个实现。我什至实现了我自己的共享指针。这让我有一个更长的链表,但也出现了析构函数中的递归和堆栈溢出。而且我看不出如何在没有析构函数递归的情况下实现共享指针。

编辑:

为了避免混淆:我想重申一下,整个列表都可以共享。所以我们可以称它们为树。

示例如下:

list1 包含:1,2,3,4,5,6,7。

list2 包含:6,6,6,1,2,3,4,5,6,7

list3 包含:10,11,12,1,2,3,4,5,6,7

我想在 3 个 SharedLinkedList 中表示这一点,它们不会通过多次存储 1、2、3、4、5、6、7 来浪费内存,但它们指向同一个地方。这就是需要引用计数的原因。

delete list3; 应该只删除未共享的部分,即元素 10、11、12。

【问题讨论】:

据我了解,问题是你的SharedLinkedList的析构函数的实现。它显然调用第一项的析构函数,然后调用下一项的析构函数,以此类推。您应该能够轻松更改 SharedLinkedList 析构函数的实现,使其不使用递归函数调用(例如,在列表元素上使用 while-loop)。 为什么不使用标准的std::list(或std::vectorstd::shared_ptr @jogojapan 看起来SharedLinkList 使用了编译器生成的析构函数。而且您不能真正添加​​一个遍历所有剩余列表的列表;重点是要对节点进行引用计数。 @jamesdlin 好吧,您需要将列表的实现与列表项的实现分开。然后你可以避免递归。 (我的理解是,引用计数是必要的,因为同一个项目可能由多个列表保存,而不是因为它们有助于列表实现本身。如果只是这样,unique-ptr 无论如何就足够了。) @jogojapan 我不确定你是否理解我分享的内容。请查看我所做的修改。 【参考方案1】:

如果您使用shared_ptr,它将为您管理所有权。当引用计数变为 0 时,它将调用指针对象的析构函数。现在指向的对象被破坏,并且作为它的一个元素,下一个共享指针破坏了下一个 ... 。这导致以递归方式释放内存。现在您可以尝试迭代地释放内存。您只需要保留对下一个元素的引用以避免其破坏并稍后手动删除它:

void destroy(SharedLinkedList* l) 
  auto next=l->next;  // take 2nd element 
  delete l;           // delete first

  while (next)
    next=next->next;  // move next forward, deleting old next 
  

【讨论】:

delete l; 会减少底层共享指针中的引用数,这也会开始递归。 @JohnBumper 不,首先创建 next 的副本。所以第二个元素的引用计数是(至少)2。当l 被删除时,它是1。这个想法是在丢失第一个元素之前将shared_ptr 复制到第二个元素。所以当第一个元素被删除时,第二个元素不会被删除。 你是对的。我不敢相信这么短的代码真的有效。但它确实有效。这就像一个奇迹。特别是next=next-&gt;next; 可以删除乍一看很奇怪的东西,但它可以。由于节省时间,我只需要特别注意不要迭代共享部分。将代码移至析构函数也很棘手,因为 next=next-&gt;next 行也调用析构函数。但我设法做到了,它运行得很快,完美无缺,而且不会破坏堆栈。祝贺您的友善,非常感谢。 可以加 if(next.use_count() > 1) break;在下一个=下一个->下一个之后。这将大大加快速度,将其从潜在的指数删除时间加速到节点数量的线性。 @ithenoob 你能解释一下你是如何达到潜在的指数删除时间的吗?添加if (next.use_count () &gt; 1) break; 将导致潜在的竞争条件。即使测试通过,其他所有者也可能在destroy 破坏next 之前释放了该对象。【参考方案2】:

一般来说,shared_ptr 可能不是 链接列表,出于您指出的原因。在这种情况下,您 可能必须手动完成,保持父母人数 每个节点。 (可能有可能解决某种 使用shared_ptr 避免堆栈溢出的循环,但是 结果可能比管理内存更复杂 手工。)

【讨论】:

以上是关于共享指针递归删除递归数据结构,堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

具有递归数据结构的 Boost 序列化最终导致堆栈溢出

基础篇8 # 递归:如何避免出现堆栈溢出呢?

递归过程中的第一个过程调用发生堆栈溢出

递归 - 堆栈溢出错误

在递归函数中处理大数组时堆栈溢出

为啥增加递归深度会导致堆栈溢出错误?