C++/std::shared_ptr

std::shared_ptr

  1. 当最后一个指向对象的 std::shared_ptr 被销毁时,引用计数变为零,对象被自动删除
  2. 循环引用是指两个或多个对象通过 std::shared_ptr 相互持有对方,导致它们的引用计数永远无法归零,从而造成内存泄露。解决方案是使用 std::weak_ptr 来打破所有权循环

std::shared_ptr 的工作原理

std::shared_ptr 系统包含两个部分:

  1. shared_ptr 对象本身:它像一个普通指针,指向被管理的对象
  2. 控制块 (Control Block) :这是一个在堆上与被管理对象一起(或分开)分配的独立内存块。所有指向同一个对象的 shared_ptr 都会共享这一个控制块。控制块包含:
    1. 强引用计数 (Strong Reference Count) :记录有多少个 shared_ptr 正在共享该对象。这是决定对象生死的关键
    2. 弱引用计数 (Weak Reference Count) :记录有多少个 weak_ptr 正在观察该对象
    3. 指向被管理对象的指针

工作流程:

  1. 当一个新的 shared_ptr 拷贝或赋值自另一个 shared_ptr 时,强引用计数 +1
  2. 当一个 shared_ptr 被销毁或指向其他对象时,强引用计数 -1
  3. 当强引用计数降为零 时,shared_ptr 会删除被管理的对象,并随后释放控制块

循环引用问题

当两个对象通过 shared_ptr 形成一个"引用环"时,它们的生命周期将永久相互依赖,无法被打破

c++ 复制代码
#include <iostream>
#include <memory>

struct Node {
    std::shared_ptr<Node> next_;
    Node() { std::cout << "Node created\n"; }
    ~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
    // 创建两个节点
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();

    // 让它们相互引用,形成一个环
    node1->next_ = node2; // node2的引用计数变为2 (main里的node2 + node1里的next_)
    node2->next_ = node1; // node1的引用计数变为2 (main里的node1 + node2里的next_)

    std::cout << "Leaving main...\n";
} // main函数结束
  1. main函数结束,node1 (栈上的智能指针) 被销毁,它将 Node A 的强引用计数从2减为1。但计数不为零,所以 Node A 不会被删除
  2. main函数结束,node2 (栈上的智能指针) 被销毁,它将 Node B 的强引用计数从2减为1。但计数不为零,所以 Node B 不会被删除
  3. 最终,Node A 持有 Node B 的引用,Node B 持有 Node A 的引用。它们在内存中相互"支撑",谁也无法被释放,导致内存泄露 。析构函数 Node destroyed 从未被打印

std::weak_ptr

C++11 提供了 std::weak_ptr 来解决这个问题。weak_ptr 是一个"观察者",它不增加强引用计数

  1. std::weak_ptr :它指向由 shared_ptr 管理的对象,但不拥有该对象。它的存在与否不影响对象的生命周期。它只是告诉:那个对象还在不在?

c++ 复制代码
#include <iostream>
#include <memory>

struct Node {
    // next_仍然是强引用,代表"拥有"下一个节点
    std::shared_ptr<Node> next_;
    
    // prev_是弱引用,代表"观察"上一个节点,不增加引用计数
    std::weak_ptr<Node> prev_;

    Node() { std::cout << "Node created\n"; }
    ~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();

    node1->next_ = node2; // node2的强引用计数为2
    node2->prev_ = node1; // node1的强引用计数为1 (prev_是weak_ptr,不增加计数)

    std::cout << "Leaving main...\n";
}
  1. main结束,栈上的 node2 智能指针被销毁。Node B 的强引用计数从2减为1。不为零,不删除
  2. main结束,栈上的 node1 智能指针被销毁。Node A 的强引用计数从1减为0。Node A 被删除
  3. 在 Node A 的析构函数中,其成员 next_ (一个指向 Node B 的 shared_ptr) 被销毁。Node B 的强引用计数从1减为0。Node B 被删除!
  4. 内存被成功释放

如何使用 weak_ptr? 因为 weak_ptr 不拥有对象,所以在访问它指向的对象之前,必须先用 lock() 方法将其提升为一个临时的 shared_ptr,以确保在访问期间对象不会被销毁

c++ 复制代码
// 假设有一个 weak_ptr<Node> w_ptr;
if (auto s_ptr = w_ptr.lock()) { // lock()返回一个shared_ptr
    // 如果对象还存在,s_ptr就是有效的
    // 在这个if作用域内,可以安全地使用 s_ptr
} else {
    // 对象已经被销毁
}
相关推荐
坤坤子吖2 小时前
Linux网络编程:TCP服务器的多进程与多线程实现
linux·服务器·开发语言·网络·c++·笔记·学习
一晌小贪欢3 小时前
Python基础:第 09 篇 · while 循环:重复直到不满足
开发语言·c++·python·数据可视化·面向对象·python办公
xiaoye-duck4 小时前
《C++ 项目:手写大模型接入 SDK》LLMManager架构设计与实现
开发语言·网络·c++·语言模型
一条大祥脚4 小时前
CuTe 与 CuTe DSL:从 C++ 模板元编程到 Python DSL 的 GPU 编程演进
开发语言·c++·python·cutlass·cute·cute dsl
·present·4 小时前
CSAPP(lecture 13_02 Linking)
c语言·c++
旖旎夜光5 小时前
【C++ 基础入门】C++ 多态(上):虚函数、重写、override/final 与抽象类
开发语言·c++·笔记·学习
青少儿编程课堂7 小时前
蓝桥杯青少组图形化 STEMA 省考(报名倒计时·11/22开考)+ 全国青少年信息素养大赛 2026—2027赛季 + 电子学会图形化考级:校园华容道大闯关
c++·python·算法·bfs·信息学竞赛
白杨尚青7 小时前
C++进阶篇(五):红黑树——四条规则、三种插入情况与旋转变色
开发语言·数据结构·c++·stl·红黑树·平衡二叉树
旖旎夜光8 小时前
【C++ 基础入门】C++ 继承(上):概念、切片、隐藏、默认成员函数与友元
c++·笔记·学习
apcipot_rain8 小时前
C++转Java日记(05)
java·开发语言·c++