我已经转换了以下链接列表结构
struct node {
node* next;
int v;
};
进入c ++ 11版本 - 没有使用指针。
struct node {
unique_ptr<node> next;
int v;
};
添加,删除元素和遍历工作正常,但是当我插入大约1mil的元素时,在调用头节点的析构函数时会出现堆栈溢出。
我不确定我做错了什么。
{
node n;
... add 10mill elements
} <-- crash here
答案 0 :(得分:7)
正如其他答案中所解释的那样,由于递归的隐式析构函数,你会出现段错误。可以在不使用原始指针的情况下解决这个问题,不得不信任编译器或编写自定义分配器:
~node() {
for (std::unique_ptr<node> current = std::move(next);
current;
current = std::move(current->next));
}
在这里,您可以迭代地浏览指针链。这将一个一个地取消一个指针,并将所有权std::move(current->next)
更改为当前。与此同时,由current
拥有的前一个未链接指针将在被移动分配覆盖时被释放。
您可能会发现显式变体更直接:
current.reset(current->next.release()));
实际上与:
相同current = std::move(current->next));
我更喜欢move
版本,因为它在任何时候都不会留下原始指针。但在这种情况下,它没有任何区别。
答案 1 :(得分:5)
你这里没有错。
当你创建1000万个元素的列表时,用make_unique
分配每个节点一切都很好(当然数据不在堆栈上,除了第一个节点!)。
问题是当你摆脱列表的头部时:unique_ptr
将负责删除它拥有的下一个节点,其中还包含一个unique_ptr
,它将负责删除下一个节点......等等......
因此,最终会以递归方式删除10百万个元素,每个递归调用都会在堆栈上占用一些空间。
答案 2 :(得分:5)
默认情况下std::unique_ptr
会调用仅执行运算符std::default_delete
的结构delete
的运算符函数。
因此结构std::default_delete
的每个运算符函数以递归方式为结构next
的数据成员node
调用。
结果你得到了堆栈溢出。
如果您使用普通指针而不是类型std::unique_ptr
的指针但是以下列方式向结构节点添加了析构函数,您将得到相同的结果
struct node {
node* next;
int v;
~node() { delete next; }
};
甚至喜欢
struct node {
node* next;
int v;
~node() { if ( next ) delete next; }
};
表示具有大量节点的列表,因为析构函数将以递归方式调用
答案 3 :(得分:3)
因为当你破坏head节点元素时,它会调用析构函数unique_ptr,它会破坏调用第3个元素的析构函数的第二个元素,该元素调用...etс1mil次。
所以,你有1 mil嵌套的函数调用(析构函数)。每个函数调用至少占用堆栈中的内存来存储返回地址(以及参数和局部变量,如果需要的话)。当然,堆栈不能提供这样的内存量。您应该重新编写代码来解决它。例如,重写Node类的析构函数,以便它找到最后一个列表元素,然后在cicle中从末尾销毁它和所有其他节点,而不是再推迟。