C++ 开发手册C++

CHAPTER 04基础层 · MEMORY

内存管理

new/delete、智能指针、栈与堆、内存泄漏与 valgrind。C++ 把内存控制权交给你,也把责任交给你——用 RAII 与智能指针守住底线。

开始阅读难度进阶先修第二、三章约2.5小时
04.1

栈与堆

局部对象在栈上,作用域结束自动析构,快且安全;new 在堆上分配,需手动 delete,慢且易泄漏。优先栈上对象。

维度栈堆
分配速度极快(移动栈指针)较慢(需查找空闲块)
生命周期作用域结束自动释放手动 delete 或智能指针
大小有限大
04.2

new 与 delete

裸 new 必须配对 delete(数组用 delete[])。现代 C++ 几乎不该手写 new/delete——交给智能指针或容器。

int* p = new int(42);
// ... 使用 p
delete p;          // 必须,且只一次
p = nullptr;       // 置空,避免悬垂指针
裸指针三宗罪

忘记 delete 泄漏;重复 delete 未定义行为;异常路径跳过 delete 泄漏。智能指针一次性解决这三者。

04.3

unique_ptr

std::unique_ptr 表达独占所有权:同一时刻只有一个指针拥有对象,不可拷贝,可移动。零额外开销,是默认首选。

auto p = std::make_unique<Widget>(42);  // 推荐 make_unique
// 离开作用域自动 delete
std::unique_ptr<Widget> q = std::move(p); // 转移所有权
优先 make_uniquemake_unique 更简洁、异常安全(避免 new 与构造间的内存泄漏窗口)。

04.4

shared_ptr

std::shared_ptr 表达共享所有权:用引用计数,最后一个持有者析构时才释放。开销高于 unique_ptr,仅在确需共享时用。

auto a = std::make_shared<Widget>();
auto b = a;                 // 引用计数 +1
// a、b 都离开作用域后对象才释放
循环引用

两个 shared_ptr 互相持有会形成引用计数永远不为 0 的环,导致泄漏。环的一侧改用 weak_ptr 打破。

04.5

weak_ptr

std::weak_ptr 是 shared_ptr 的非拥有观察者:不增加引用计数,用 lock() 临时升级为 shared_ptr 使用。

std::weak_ptr<Widget> w = a;   // 不增加计数
if (auto s = w.lock()) {          // 提升成功说明对象还在
  s->use();
}
04.6

所有权原则

每个资源都有唯一"负责人":函数参数用 const T& 或 shared_ptr<const T> 表示"借用/共享",返回值用 unique_ptr 表示"移交所有权"。

// 工厂返回独占所有权
std::unique_ptr<Widget> create() {
  return std::make_unique<Widget>();
}
04.7

泄漏检测

用 AddressSanitizer(编译加 -fsanitize=address)或 valgrind 检测内存错误与泄漏,纳入 CI 自动把关。

g++ -std=c++20 -fsanitize=address -g main.cpp -o main
./main            # 运行时报告泄漏/越界
ASan 优于 valgrind

ASan 性能好、定位准、能抓越界与 use-after-free;valgrind 更慢但无需重编译,二者互补。

04.8

常见陷阱与 FAQ

unique_ptr 能放进容器吗?

可以,用 std::vector<std::unique_ptr<T>>,元素间所有权独立,移动语义保证安全。这是管理多态对象集合的惯用法。

this 能用 shared_from_this 吗?

需要把"自身"作为 shared_ptr 传出去时,让类继承 std::enable_shared_from_this,用 shared_from_this(),避免重复构造控制块。

智能指针比裸指针慢很多?

unique_ptr 与裸指针开销几乎一致(编译期信息);shared_ptr 有原子引用计数开销,但远小于手写管理的隐性 bug 成本。

04.9

实战练习与资源

延伸资源