使用shared_ptr实现Deque时节点未正常析构的问题求助
你遇到的问题是典型的shared_ptr循环引用引发的内存泄漏。我们来拆解一下为什么引用计数会是3,以及节点无法被析构的原因:
- 对于
node1,它的强引用来自三个地方:- main函数里的
node1变量 - Deque类的
head指针 node2的prev指针(因为你用了shared_ptr,属于强引用)
- main函数里的
- 对于
node2,它的强引用同样来自三个地方:- main函数里的
node2变量 - Deque类的
tail指针 node1的next指针(强引用)
- main函数里的
当程序结束时,Deque的析构函数会销毁head和tail,这会让node1和node2的引用计数各减1(变成2)。接着main函数里的node1和node2被销毁,计数再各减1(变成1)。但此时node1和node2还互相持有对方的强引用,形成了一个闭环——没有外部强引用指向它们,但内部互相强引用的状态会让引用计数永远无法降到0,析构函数自然不会被调用。
解决方案:用weak_ptr打破循环引用
解决这个问题的核心是把链表中单向的强引用改成弱引用,比如把节点的prev指针换成std::weak_ptr(选next方向也可以,只要打破闭环就行)。弱引用不会增加shared_ptr的引用计数,也就不会形成强引用循环。
修改后的完整代码如下:
#include<iostream> #include<memory> class Node { int value; std::shared_ptr<Node> next = nullptr; std::weak_ptr<Node> prev = nullptr; // 将prev改为weak_ptr public: Node() = default; Node(int val): value(val) {} ~Node() { std::cout << "Destructor of node: " << value << std::endl; } friend class Deque; }; class Deque { using pointer = std::shared_ptr<Node>; pointer head = nullptr; pointer tail = nullptr; public: Deque() = default; ~Deque(){ std::cout << "Dequeue destructor" << std::endl; } bool is_empty() { return head == nullptr && tail == nullptr; } void push_back(const pointer& val) { if (is_empty()) { head = val; tail = val; } else { val->prev = tail; // shared_ptr可以直接赋值给weak_ptr tail->next = val; tail = val; } } }; int main() { Deque DEQ; auto node1 = std::make_shared< Node >(1); auto node2 = std::make_shared< Node >(2); auto node3 = std::make_shared< Node >(3); DEQ.push_back(node1); DEQ.push_back(node2); std::cout << "Use count node1 = " << node1.use_count() << std::endl; std::cout << "Use count node2 = " << node2.use_count() << std::endl; std::cout << "Use count node3 = " << node3.use_count() << std::endl; return 0; }
修改后的运行效果
运行修改后的代码,你会看到所有节点都能被正确析构,输出如下:
Use count node1 = 2 Use count node2 = 3 Use count node3 = 1 Destructor of node: 3 Dequeue destructor Destructor of node: 2 Destructor of node: 1
这里的引用计数变化也符合预期:
node1的计数是2:来自main的node1和Deque的head(node2->prev是弱引用,不参与计数)node2的计数是3:来自main的node2、Deque的tail、node1->next(强引用)
程序结束时的析构流程:
node3先被析构(计数降到0)- Deque析构,
head和tail被销毁,node1计数减1(变成1),node2计数减1(变成2) - main里的
node1被销毁,计数减1(变成0),node1析构,同时它的next(指向node2)被销毁,node2计数减1(变成1) - main里的
node2被销毁,计数减1(变成0),node2析构
这样所有节点都能被正确回收,解决了内存泄漏问题。
内容的提问来源于stack exchange,提问作者Cantaro
相关产品推荐
相关产品推荐

