You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用shared_ptr实现Deque时节点未正常析构的问题求助

问题分析:循环引用导致shared_ptr无法正确析构

你遇到的问题是典型的shared_ptr循环引用引发的内存泄漏。我们来拆解一下为什么引用计数会是3,以及节点无法被析构的原因:

  • 对于node1,它的强引用来自三个地方:
    1. main函数里的node1变量
    2. Deque类的head指针
    3. node2的prev指针(因为你用了shared_ptr,属于强引用)
  • 对于node2,它的强引用同样来自三个地方:
    1. main函数里的node2变量
    2. Deque类的tail指针
    3. node1的next指针(强引用)

当程序结束时,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(强引用)

程序结束时的析构流程:

  1. node3先被析构(计数降到0)
  2. Deque析构,head和tail被销毁,node1计数减1(变成1),node2计数减1(变成2)
  3. main里的node1被销毁,计数减1(变成0),node1析构,同时它的next(指向node2)被销毁,node2计数减1(变成1)
  4. main里的node2被销毁,计数减1(变成0),node2析构

这样所有节点都能被正确回收,解决了内存泄漏问题。

内容的提问来源于stack exchange,提问作者Cantaro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 13:22:37