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
}
相关推荐
anew___4 小时前
蜂鸣器播放音乐——让 Arduino 唱出旋律
c++·stm32·单片机·嵌入式硬件·c
怕浪猫4 小时前
长会话不崩盘:DeepSeek Harness 的上下文压缩与目标管理策略
面试·github·agent
ocean21035 小时前
2025-2026年Python面试高频知识点洞察
开发语言·python·面试·python八股文
stolentime5 小时前
洛谷P10515 转圈题解
c++·算法·数学建模·贪心算法
tdtsmt6 小时前
穿戴硬件量产工艺实录:天地通电子智能手表主板 SMT 贴片案
c++
letisgo56 小时前
JAVA 高级进阶07篇《@Transactional失效的8个场景:代理机制到传播行为》
java·面试·transactional·spring事务·aop动态代理
黄敬峰9 小时前
一文搞懂 LangGraph 分支、循环与状态持久化
面试
m0_734571769 小时前
深入理解C++ 析构函数<一>基本概念
开发语言·c++
t-think10 小时前
C++ string 类(上)—— string类初识与常用接口详解
开发语言·c++
cpp_learners11 小时前
C++ 实现责任链模式(Chain of Responsibility):从一堆 if-else 到可插拔的处理管道
开发语言·c++·责任链模式