C++智能指针:从内存裸奔到安全驾驶(附保姆级代码示例)

[复制链接]
发表于 2025-10-21 12:39:18 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

各人好呀,我是灰灰,上期咱们聊完引用,不少小搭档在品评区哭诉内存走漏的凄惨履历。本日咱们就来解锁C++的"自动驾驶"神器——智能指针!今后告别new/delete的手动挡期间,系好安全带,发车啦!🚗💨

一、智能指针是什么?为什么须要它?

1.1 手动管理内存的痛
  1. void 作死示例() {
  2.     int* 裸指针 = new int[10086];  // 申请
  3.     // ... 一顿操作猛如虎 ...
  4.     if(rand() % 2) return;       // 50%概率忘记释放!
  5.     delete[] 裸指针;              // 内存泄漏的万恶之源
  6. }
复制代码
经典翻车现场:

  • 忘记delete → 内存走漏
  • 提前delete → 野指针
  • 非常导致跳过delete → 走漏
1.2 智能指针的救世主本质

智能指针就像你的私人管家,它本质上是一个类,当失去作用时会调用其析构函数,自动开释内存:
1️⃣ 自动开释:对象烧毁时自动整理内存
2️⃣ 非常安全:哪怕步调瓦解也不走漏
3️⃣ 全部权明确:清晰表达资源归属
二、智能指针创建三大姿势

智能指针的根本模板是:
  1. std::unique_ptr<T, DeleterType> ptr(原始指针, 删除器);
复制代码
因此我们只须要将须要管理的指针交给它托管。
(删除器是可选项,默认是利用delete或delete[],但是遇到一些不能通过delete开释的资源,好比管理fopen()返回的FILE*(需用fclose()关闭),就须要自界说删除器了,如果有须要以后会有专门一篇文章解说哦)
1.1 直接构造法(传统技能)
  1. // 裸指针转智能指针(慎用!)
  2. std::unique_ptr<int> 管家1(new int(42));
  3. std::shared_ptr<std::string> 管家2(new std::string("Hello"));
复制代码
实用场景:

  • 须要自界说删除器时
  • 兼容老旧代码
伤害告诫:
  1. int* 裸指针 = new int(10);
  2. std::shared_ptr<int> 管家A(裸指针);
  3. std::shared_ptr<int> 管家B(裸指针);
  4. // 双重释放!两个管家各自为战
复制代码

1.2 工厂函数法(官方保举)
  1. auto 安全管家1 = std::make_unique<int>(42);      // C++14+
  2. auto 安全管家2 = std::make_shared<std::string>("Hello");
复制代码
上风对比:
创建方式内存分配次数非常安全代码简便度new + 构造函数2次❌❌make_shared1次✅✅代码示例:
  1. // 创建数组的专属姿势
  2. auto 数组管家 = std::make_unique<int[]>(5);  // 5个int的数组
  3. 数组管家[0] = 1;  // 像普通数组一样使用
  4. // 自定义类型
  5. class 学生 {
  6. public:
  7.     学生(string 名, int 分) : 名字(名), 分数(分) {}
  8. };
  9. auto 学霸 = std::make_shared<学生>("张三", 100);
复制代码

1.3 空指针初始化法(耽误加载)
  1. std::shared_ptr<文件类> 文件管家 = nullptr;
  2. if(需要加载文件) {
  3.     文件管家 = std::make_shared<文件类>("data.bin");
  4. }
复制代码
当创建好智能指针后,你完全可以把它看成一个平凡的指针去利用它,但是牢记不能delete哦(这是管家的工作)

三、三大智能指针详解

2.1 unique_ptr:埋头霸道总裁

特点:

  • 独占资源,拒绝共享(move-only)
  • 零额外开销(和裸指针一样高效)
利用场景:

  • 明确资源唯一全部者的场景
  • 替换auto_ptr(C++17已废弃)
代码实战:
  1. // 创建独占文件句柄
  2. std::unique_ptr<FILE, decltype(&fclose)> 文件管家(fopen("data.txt", "r"), fclose);
  3. // 自动管理动态数组
  4. auto 数组管家 = std::make_unique<int[]>(100);
  5. // 所有权转移(交出控制权)
  6. auto 新管家 = std::move(数组管家);
  7. // 此时数组管家变为nullptr
复制代码
避坑指南:

  • ❌ 克制拷贝构造(会编译报错)
  • ✅ 用reset()自动开释资源
  • ✅ 通过get()获取裸指针(但不要手动delete!)

2.2 shared_ptr:共享经济达人

特点:

  • 通过引用计数共享资源
  • 计数归零自动开释
  • 支持自界说删除器
代码实战:
  1. class 大对象 {
  2. public:
  3.     ~大对象() { cout << "我挂了..."; }
  4. };
  5. // 创建共享资源
  6. auto 共享资源 = std::make_shared<大对象>();
  7. {
  8.     auto 副本1 = 共享资源;  // 引用计数+1
  9.     auto 副本2 = 共享资源;  // 引用计数+1 → 3
  10. } // 离开作用域计数-2 → 1
  11. } // 最后离开作用域计数归零,触发析构
复制代码
经典翻车现场:循环引用
  1. class 甲 {
  2. public:
  3.     std::shared_ptr<乙> 乙指针;
  4. };
  5. class 乙 {
  6. public:
  7.     std::shared_ptr<甲> 甲指针;
  8. };
  9. auto 甲哥 = std::make_shared<甲>();
  10. auto 乙哥 = std::make_shared<乙>();
  11. 甲哥->乙指针 = 乙哥;
  12. 乙哥->甲指针 = 甲哥;  // 引用计数永远≥1,内存泄漏!
复制代码

2.3 weak_ptr:突破循环的钥匙

特点:

  • 观察者模式,不增长引用计数
  • 需通过lock()获取可用的shared_ptr
救济循环引用:
  1. class 乙 {
  2. public:
  3.     std::weak_ptr<甲> 甲指针;  // 改用weak_ptr
  4. };
  5. auto 甲哥 = std::make_shared<甲>();
  6. auto 乙哥 = std::make_shared<乙>();
  7. 甲哥->乙指针 = 乙哥;
  8. 乙哥->甲指针 = 甲哥;  // 现在可以正确释放了!
  9. // 使用示例
  10. if(auto 临时管家 = 乙哥->甲指针.lock()) {
  11.     临时管家->操作();  // 安全访问
  12. } else {
  13.     cout << "对象已释放";
  14. }
复制代码

四、智能指针性能揭秘

3.1 性能对比表

指针范例内存开销线程安全实用场景unique_ptr0额外开销非线程安全独占资源、性能敏感shared_ptr2倍指针巨细引用计数原子利用共享资源weak_ptr2倍指针巨细同shared_ptr突破循环引用3.2 最佳实践

1️⃣ 优先利用make_shared/make_unique:

  • 克制内存碎片
  • 非常安全(不会出现new完没赋给指针的环境)
❌ 伤害利用:
  1. process(shared_ptr<大对象>(new 大对象), 其他函数());
  2. // 若`其他函数()`抛出异常,可能泄漏!
复制代码
✅ 安全写法:
  1. process(std::make_shared<大对象>(), 其他函数());
复制代码

五、智能指针常用函数手册

3.1 通用函数百口桶

函数作用示例reset()开释当前资源管家.reset();get()获取裸指针(慎用!)int* p = 管家.get();operator->访问成员管家->存款 += 100;operator*解引用int value = *管家;operator bool判断是否为空if(管家) { ... }
3.2 shared_ptr专属技能

函数作用示例use_count()查察引用计数cout << 管家.use_count();unique()判断是否唯一全部者if(管家.unique()) { ... }swap()交换两个shared_ptr管家A.swap(管家B);
3.3 unique_ptr黑科技

函数作用示例release()开释全部权返回裸指针int* p = 管家.release();swap()交换两个unique_ptr管家A.swap(管家B);
六、避坑宝典(血泪履历)

❌ 大坑1:混用裸指针和智能指针
  1. auto 管家 = std::make_shared<大对象>();
  2. 大对象* 裸指针 = 管家.get();
  3. delete 裸指针;  // 导致双重释放!
  4. 管家->操作();    // 程序崩溃!
复制代码
❌ 大坑2:在多个shared_ptr中托管同一裸指针
  1. auto* 裸指针 = new 大对象;
  2. std::shared_ptr<大对象> 管家1(裸指针);
  3. std::shared_ptr<大对象> 管家2(裸指针);
  4. // 双重释放!引用计数不共享
复制代码
✅ 精确姿势:始终利用make_shared
  1. auto 安全管家1 = std::make_shared<大对象>();
  2. auto 安全管家2 = 安全管家1;  // 共享计数
复制代码

七、总结:智能指针三定律

1️⃣ 能用智能指针就不消裸指针:

  • 除非和C API交互,其他环境无脑用
2️⃣ 能unique就不shared:

  • 优先用unique_ptr,必须共享再用shared_ptr
3️⃣ 循环引用必用weak_ptr:

  • 遇到环形引用关系,立刻掏出weak_ptr破局

智能指针就像C++天下的自动驾驶体系——你负责指明方向(业务逻辑),它负责安全驾驶(内存管理)。下期想听什么?品评区告诉!咱们《C++移动语义:从深拷贝到零本钱搬运》见! 🚀
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表