使用std::shared_ptr实现双向链表,删除节点需清空其指针吗?
我正在用C++实现基于std::shared_ptr的双向链表,定义的Node和List结构体如下:
struct Node { std::shared_ptr<Node> prev, next; int data; Node(int data) : data(data), next(nullptr), prev(nullptr){}; Node(int data, std::shared_ptr<Node> prev, std::shared_ptr<Node> next) : data(data), prev(prev), next(next){}; ~Node(){}; }; struct List { std::shared_ptr<Node> head, tail; size_t size; // 其他方法... void remove(int data); List(); List(std::initializer_list<int> list); List(size_t s, int data = 0); ~List(); };
现在在实现remove方法时,我纠结一个问题:删除目标节点时,要不要把该节点的prev和next指针置空?我写的示例代码片段如下:
// value - 要删除的目标数值 if (curr->data == value) { curr->prev->next = curr->next; curr->next->prev = curr->prev; std::shared_ptr<Node> tmp = curr; curr = curr->next; tmp->prev = nullptr; tmp->next = nullptr; tmp = nullptr; size--; }
核心分析
先明确std::shared_ptr的核心特性:它基于引用计数管理内存,当指向对象的所有shared_ptr都被销毁/置空时,引用计数降为0,对象才会被自动释放。
1. 不置空指针的后果
当你把节点从链表中摘除(curr->prev->next = curr->next; curr->next->prev = curr->prev;)后,链表的其他节点已经不再持有指向该节点的shared_ptr,但被删除节点自身还持有prev和next的shared_ptr,这会让这两个相邻节点的引用计数多维持一个额外的计数,直到被删除节点被销毁。
不过当你把tmp置空后,指向被删除节点的shared_ptr只剩curr(但curr已经被重新赋值为curr->next),此时被删除节点的引用计数降为0,会被自动销毁,它持有的prev和next也会被析构,相邻节点的引用计数会随之减少,不会造成内存泄漏。
2. 置空指针的意义
置空tmp->prev和tmp->next是可选但推荐的操作:
- 可以让相邻节点的引用计数立即减少,而不是等到被删除节点销毁时才触发;
- 避免被删除节点在销毁前,意外通过
prev/next访问链表的其他节点(虽然你的代码中已经没有这种访问可能,但这是一种良好的编码习惯)。
3. 更关键的问题:循环引用
你当前的Node结构存在致命的循环引用问题:节点之间用shared_ptr互相指向prev和next,比如节点A的next指向节点B,节点B的prev指向节点A,此时两个节点的引用计数至少为1(互相持有),即使链表的head/tail被置空,这两个节点也永远不会被释放,导致内存泄漏。
解决这个问题的正确做法是:把Node中的prev改成std::weak_ptr<Node>,因为prev属于反向引用,用弱指针不会增加引用计数,从而避免循环引用。修改后的Node结构如下:
struct Node { std::weak_ptr<Node> prev; std::shared_ptr<Node> next; int data; Node(int data) : data(data), next(nullptr){}; Node(int data, std::weak_ptr<Node> prev, std::shared_ptr<Node> next) : data(data), prev(prev), next(next){}; ~Node(){}; };
这样,当节点被从链表中摘除后,只要没有其他shared_ptr指向它,引用计数就会降为0,节点会被正确释放,同时不会因为反向引用导致循环。
内容的提问来源于stack exchange,提问作者lian

