DAY 5-智能指针与 C++ 内存管理

1. unique_ptr vs shared_ptr vs weak_ptr

1.1 三句话说清

类型 一句话
unique_ptr 我独占,你不能拷贝,只能移交所有权
shared_ptr 大家共享,最后一个释放的负责 delete
weak_ptr 我只是看看,不参与计数,不阻止释放

1.2 所有权模型图解

ini 复制代码
unique_ptr:
  ptr ───────▶ [资源]
  独占,ptr 析构时资源释放

shared_ptr:
  sp1 ──┐
  sp2 ──┼──▶ [资源] (ref_count = 2)
  sp3 ──┘
  共享,ref_count 归零时资源释放

weak_ptr:
  wp ─ ─ ─ ▶ [资源] (ref_count 不变)
  弱引用,不阻止释放,访问需 lock()

1.3 选择原则

arduino 复制代码
需要动态分配 → 先用 unique_ptr
需要多个持有者 → 升级为 shared_ptr
需要打破循环 → 其中一个改为 weak_ptr

2. shared_ptr 陷阱

2.1 同一裸指针初始化两个 shared_ptr

arduino 复制代码
int *raw = new int(10);
shared_ptr<int> sp1(raw);   // ref_count = 1
shared_ptr<int> sp2(raw);   // ref_count = 1(另一个控制块!)

// sp1 析构 → delete raw ✅
// sp2 析构 → delete raw ❌ double free!

每个 shared_ptr 从裸指针构造时,会创建独立的控制块。 两个控制块各记各的,都以为自己是唯一的持有者。

正确做法:

arduino 复制代码
auto sp1 = make_shared<int>(10);  // 首选
shared_ptr<int> sp2 = sp1;        // 拷贝,ref_count = 2

2.2 循环引用

css 复制代码
class B;  // 前置声明
class A {
public:
    shared_ptr<B> bptr;
    ~A() { cout << "~A"; }
};
class B {
public:
    shared_ptr<A> aptr;  // ← 如果这是 weak_ptr 就能打破循环
    ~B() { cout << "~B"; }
};

{
    auto a = make_shared<A>();
    auto b = make_shared<B>();
    a->bptr = b;   // b 的 ref_count = 2
    b->aptr = a;   // a 的 ref_count = 2
}  // a,b 出作用域,各自 ref_count 降到 1,但互指无法归零
   // 析构函数永远不会被调用,内存泄漏!
css 复制代码
循环引用图解:
  a ──shared_ptr──▶ b
  ▲                │
  └──shared_ptr────┘
  ref_count(a) = 1(来自 b)
  ref_count(b) = 1(来自 a)
  无法释放

解决方案:

arduino 复制代码
class B {
public:
    weak_ptr<A> aptr;  // ✅ 不增加引用计数
};

weak_ptr 使用方式:

scss 复制代码
if (auto sp = wp.lock()) {   // 提升为 shared_ptr
    sp->do_something();       // 安全使用
} else {
    // 资源已被释放
}

3. new/delete vs malloc/free

new/delete malloc/free
本质 C++ 运算符 C 库函数
调用构造函数 ✅ new 时调用构造 ❌ 只分配内存
调用析构函数 ✅ delete 时调用析构 ❌ 只释放内存
失败行为 抛 bad_alloc 异常 返回 NULL
返回类型 具体类型指针 void* 需强转
大小计算 编译器自动算 手动指定字节数
可重载 ✅ ❌

4. delete vs delete\[\]

arduino 复制代码
int *p = new int[10];
delete p;    // ❌ 未定义行为
delete[] p;  // ✅ 正确
delete delete\[\]
配对 new new[]
析构调用 只调第一个 调所有元素
内置类型后果 可能不崩但未定义 正确
类类型后果 必定泄漏 正确

5. 移动语义

5.1 拷贝 vs 移动

makefile 复制代码
拷贝:  src → 复制数据 → dst    (src 不变,成本高)
移动:  src → 偷走指针 → dst    (src 被掏空,成本极低)

5.2 std::move vs std::forward

std::move std::forward
作用 无条件转右值 条件转发
语义 "我要移动" "保持原样传下去"
使用场景 已知要移动时 模板完美转发
实际上做了什么 强制类型转换 条件类型转换
typescript 复制代码
template <typename T>
void relay(T &&arg) {
    // forward:arg 是左值 → 转左值,arg 是右值 → 转右值
    target(forward<T>(arg));
}

void target(string &s)  { cout << "左值"; }
void target(string &&s) { cout << "右值"; }

5.3 移动构造函数的最大陷阱

scss 复制代码
// ❌ 错误写法
Data(Data &&other) : ptr(other.ptr) {}
// 析构时 other 和 this 都指向同一内存 → double free

// ✅ 正确写法
Data(Data &&other) : ptr(other.ptr) {
    other.ptr = nullptr;  // 必须置空源对象指针 不然因为指针值复制,double free
}
相关推荐
hetao173383711 小时前
2026-10-22~23 hetao1733837 的刷题记录
c++·算法
SailingCoder12 小时前
【封闭结束1】端到端交付:Agent 一次请求如何走完全链路
前端·安全·ai·面试·agent·harness
王老师青少年编程12 小时前
2025年6月GESP真题及题解(C++三级): 选择题和判断题(题解)
c++·真题·gesp·答案·csp·三级·信奥赛
Interview Aid11213 小时前
Uber Interview Process 完整指南:面试流程、OA 真题与 VO 备考
面试·职场和发展
卡卡~~13 小时前
“101计划”核心课程实践教材《数据实验结构指导-C++语言版》题目集
开发语言·c++·学习·算法
lengxuenong14 小时前
树状数组 · 信息学学习手册1-求和3个模板
c++·学习·算法·青少年编程
zhangminghuan14 小时前
前端面试应对技巧
前端·面试·职场和发展
ShineWinsu15 小时前
对于Qt:信号与槽的解析
c++·qt·gui·lambda·信号·信号与槽·槽函数
WL学习笔记15 小时前
使用string类
c++·后端·string类
xxxiugou12315 小时前
链表算法题精讲:从两数相加到K组翻转
数据结构·c++·算法·链表