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
}
相关推荐
(Charon)1 小时前
【C++】手写 MySQL 连接池(三):异步任务队列与工作线程
c语言·c++
FogLetter1 小时前
进程VS线程:你的电脑到底是怎么同时干那么多活的?
前端·面试
yyds_yyd_100861 小时前
877. 石子游戏(2026.08.02)& 486. 预测赢家(2026.08.01)
c++·leetcode
FogLetter1 小时前
我真的写了个“诈尸式”缓存组件:手撕React KeepAlive
前端·react.js·面试
小小帅呀2 小时前
CUDA编程实战12:原子操作与高性能直方图——从正确累加到低冲突并行更新
c++·人工智能·线性代数·矩阵
加油码2 小时前
共享内存详解:原理、系统调用与高性能进程间通信
linux·c语言·c++
Lzh编程小栈2 小时前
【STM32底层精讲】RCC时钟系统超全详解(时钟树+源码+避坑指南)
c语言·stm32·单片机·嵌入式硬件·面试
June`3 小时前
常量内存和只读缓存
c++·人工智能·算法·cuda
Cx330❀3 小时前
【Linux网络】深入 HTTP 协议(五):从 Cookie/Session 原理到 C++ 源码实战
linux·运维·服务器·开发语言·网络·c++·http