C++ 内存管理的三大时代
| 时代 | 代表写法 | 内存安全程度 | 现代项目是否推荐 | 备注 |
|---|
| C 风格时代 | new / delete / malloc / free | ★☆☆☆☆ | 几乎不推荐 | 极易泄漏、双 delete、悬垂指针 |
| 半现代时代 | auto_ptr + 手动 new/delete | ★★☆☆☆ | 已过时 | C++98/03 时代的过渡产物 |
| 现代 C++ | RAII + 智能指针 + 容器 | ★★★★★ | 强烈推荐 | C++11 之后的主流写法 |
现代 C++ 内存管理的核心理念
- 谁分配谁负责释放(所有权清晰)
- 资源获取即初始化(RAII) —— 构造时获取,析构时释放
- 尽量避免显式 delete(让编译器/标准库帮你管)
- 默认使用栈 > 智能指针 > 裸指针(从安全到危险排序)
现代 C++ 中最常用的 7 种内存管理方式
| 优先级 | 方式 | 所有权语义 | 典型场景 | 是否推荐 new/delete |
|---|
| 1 | 局部变量(栈上) | 作用域结束自动销毁 | 99% 的小对象、临时变量 | 绝对不写 new |
| 2 | std::unique_ptr<T> | 独占所有权 | 需要动态生命周期,但只有一个拥有者 | 推荐 make_unique |
| 3 | std::shared_ptr<T> | 共享所有权(引用计数) | 需要多处共享、延迟销毁的场景 | 推荐 make_shared |
| 4 | std::vector<T> / std::string 等容器 | 容器负责 | 动态大小的序列、字符串 | 基本不用 new[] |
| 5 | std::weak_ptr<T> | 非拥有(弱引用) | 解决 shared_ptr 循环引用 | — |
| 6 | 自定义 RAII 封装类 | 资源(文件/锁/句柄) | 数据库连接、文件、互斥锁、socket 等 | — |
| 7 | 裸指针(作为观察者) | 无所有权 | 函数参数、回调、遍历(不负责释放) | 可以用,但慎用 |
现代写法 vs 传统写法的对比
void bad_style() {
Widget* w = new Widget(args);
if (some_condition) {
delete w;
return;
}
delete w;
}
#include <memory>
void good_style() {
auto w = std::make_unique<Widget>(args);
}
共享所有权场景(最常见的工厂模式)
class Widget { };
std::shared_ptr<Widget> createWidget(Params p) {
return std::make_shared<Widget>(p);
}
2024-2025 年最值得记住的'现代 C++ 内存管理口诀'
- 优先使用 make_unique / make_shared(异常安全 + 性能更好)
- 不要用 new 直接初始化智能指针(除非你有非常特殊理由)
- 函数参数优先用 T& / const T& / T&&,而不是 shared_ptr 传参(除非明确需要共享所有权)
- 回调/观察者模式用 weak_ptr + lock() 判断对象是否存活
- 永远不要在容器里放裸指针(除非你用自定义 deleter 或非常明确生命周期)
- 自定义资源用 RAII 封装类(文件、锁、句柄、GPU 资源等)
- 尽量少用裸 new/delete(除非你在写:分配器、内存池、极致性能场景)
常见问题快速对照表
| 问题 | 传统写法容易犯的错误 | 现代正确做法 |
|---|
| 内存泄漏 | 忘记 delete / 异常路径漏掉 | 用 unique_ptr / shared_ptr / 容器 |
| 悬垂指针 | 对象先销毁,指针还活着 | 用 weak_ptr + lock() 检查 |
| 双重释放 | 多次 delete | 智能指针自动管理 |
| 循环引用 | shared_ptr 互相指向 | 一方改用 weak_ptr |
| 性能浪费 | 频繁 new/delete | 用内存池、arena allocator、小对象优化 |
| 传参膨胀 | 到处传 shared_ptr | 优先用引用 / 原始指针(观察者语义) |
快速自测
std::make_unique 和 new 的主要区别是什么?为什么更推荐前者?
- 什么时候应该用
shared_ptr 而不是 unique_ptr?
- 函数参数收到
shared_ptr<Widget> 和 Widget* 分别代表什么语义?
- 如何用 weak_ptr 避免循环引用?
- 下面代码是否有问题?怎么改成现代写法?
std::vector<Widget*> widgets;
for (int i = 0; i < 10; i++) {
widgets.push_back(new Widget());
}