共享指针递归删除递归数据结构,堆栈溢出
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::vector
)std::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->next;
可以删除乍一看很奇怪的东西,但它可以。由于节省时间,我只需要特别注意不要迭代共享部分。将代码移至析构函数也很棘手,因为 next=next->next
行也调用析构函数。但我设法做到了,它运行得很快,完美无缺,而且不会破坏堆栈。祝贺您的友善,非常感谢。
可以加 if(next.use_count() > 1) break;在下一个=下一个->下一个之后。这将大大加快速度,将其从潜在的指数删除时间加速到节点数量的线性。
@ithenoob 你能解释一下你是如何达到潜在的指数删除时间的吗?添加if (next.use_count () > 1) break;
将导致潜在的竞争条件。即使测试通过,其他所有者也可能在destroy
破坏next
之前释放了该对象。【参考方案2】:
一般来说,shared_ptr
可能不是
链接列表,出于您指出的原因。在这种情况下,您
可能必须手动完成,保持父母人数
每个节点。 (可能有可能解决某种
使用shared_ptr
避免堆栈溢出的循环,但是
结果可能比管理内存更复杂
手工。)
【讨论】:
以上是关于共享指针递归删除递归数据结构,堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章