cs106l-lecture-6-1-raii-smart-pointers
Course: 斯坦福大学: C++标准库编程 | C++ Standard Library Programming
🎮 CS106L Lecture 6.1 — RAII & Smart Pointers
🏛 Stanford CS106L — Standard C++ Programming Language
📍 主题:资源获取即初始化(RAII)与智能指针(Smart Pointers)
🎯 目标:理解现代 C++ 资源管理思想,从手动 new/delete 过渡到自动化 RAII 架构。
🧭 课程定位
本节课承接《Special Member Functions》和《Move Semantics》,
从“资源生命周期管理”切入,揭示现代 C++ 的核心哲学:
🧠 “资源的所有权应与对象生命周期绑定,由构造负责获取,由析构负责释放。”
🏷 关键词速记
RAII|资源即初始化
Smart Pointer|智能指针
Ownership|所有权语义
Destructor|析构函数
unique_ptr|独占所有权
shared_ptr|共享所有权
weak_ptr|非拥有观察者
Exception Safety|异常安全
🎮 30 秒课程摘要
“在 C++ 中,RAII 是最安全的资源管理模式。
析构函数自动释放资源,智能指针则将 RAII 泛化为通用所有权模型。”
📚 一、RAII 核心哲学
⚙️ 1️⃣ 资源与生命周期的绑定
📦 RAII 原则:
资源(文件、锁、内存、句柄)应在对象构造时获取,并在析构时释放。
📦 示例:手动管理文件流(易错)
void processFile() {
std::ifstream file("data.txt");
if (!file.is_open()) throw std::runtime_error("File open failed");
// ... 处理文件内容 ...
file.close(); // 忘记 close() 会导致资源泄漏
}📦 改进版:RAII 自动释放
void processFile() {
std::ifstream file("data.txt"); // 构造时打开
// 作用域结束时自动析构 → 自动关闭文件
}🧠 总结:
- 构造函数负责“获取资源”
- 析构函数负责“释放资源”
- RAII 通过作用域自动管理资源,保证异常安全
📦 二、RAII 的典型应用场景
🔒 文件与锁管理
📦 示例:锁的 RAII 化管理
std::mutex mtx;
void safeOperation() {
std::lock_guard<std::mutex> guard(mtx); // 加锁
// 临界区代码
} // guard 离开作用域 → 自动解锁
🧠 要点:
std::lock_guard是 RAII 的经典封装。- 构造时加锁,析构时解锁。
- 避免手动
mtx.lock()/mtx.unlock()的遗忘。
📚 三、从 RAII 到智能指针
🧩 1️⃣ 问题:传统指针的灾难
void foo() {
int* p = new int(42);
// 忘记 delete?→ 内存泄漏!
// 多次 delete?→ 未定义行为!
}🧠 RAII 的解决方案:
→ “让对象的析构函数自动调用 delete。”
📦 四、unique_ptr(独占所有权)
⚙️ 独占语义:唯一持有资源,自动释放
📦 定义与使用
#include <memory>
void demoUniquePtr() {
std::unique_ptr<int> ptr = std::make_unique<int>(42);
std::cout << *ptr << "\n"; // 输出 42
} // 自动析构 → 自动 delete
📦 移动语义支持
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::unique_ptr<int> p2 = std::move(p1); // 转移所有权
🧠 特点:
- 无法复制(
copy constructor被删除) - 可移动(
move constructor可用) - 适用于独占资源(如文件句柄、socket、临时缓存等)
📦 代码守则
std::unique_ptr<int> x(new int(5)); // ❌ 避免显式 new
auto x = std::make_unique<int>(5); // ✅ 推荐写法
📚 五、shared_ptr(共享所有权)
⚙️ 多对象共享同一资源(引用计数机制)
📦 示例
#include <memory>
void demoSharedPtr() {
std::shared_ptr<int> a = std::make_shared<int>(10);
std::shared_ptr<int> b = a; // 引用计数 +1
std::cout << a.use_count(); // 输出 2
} // 作用域结束 → 计数归零 → 自动 delete
🧠 机制原理:
- 内部维护引用计数(reference count)
- 每次复制构造时递增计数
- 每次析构时递减计数,归零自动销毁对象
⚠️ 注意:循环引用陷阱
struct Node {
std::shared_ptr<Node> next; // ❌ 会导致循环引用
};🪄 解决:
用 std::weak_ptr 打破循环引用。
📦 六、weak_ptr(非拥有观察者)
⚙️ 解决 shared_ptr 循环依赖问题
📦 示例
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 不参与引用计数
};🧠 讲解:
weak_ptr不增加引用计数- 可通过
.lock()安全访问底层对象(若已销毁则返回空) - 常用于双向链表、图结构、缓存系统等
📚 七、make_unique & make_shared
⚙️ 为什么要用工厂函数而不是直接 new
📦 推荐用法
auto p1 = std::make_unique<MyClass>(args...);
auto p2 = std::make_shared<MyClass>(args...);🧠 优势:
- 避免两次内存分配(尤其是
shared_ptr) - 更安全:不易引发异常泄漏
- 更简洁:避免
new
📦 等价手动写法(❌ 不推荐)
std::shared_ptr<MyClass> p(new MyClass(args...)); // 可能泄漏
📚 八、异常安全与 RAII
📦 传统方式的问题
Widget* w = new Widget();
doSomething(); // 可能抛异常
delete w; // 永远不会执行
📦 RAII 改进
auto w = std::make_unique<Widget>();
doSomething(); // 即使抛异常也安全释放
🧠 总结:
- RAII 自动调用析构 → 防止资源泄漏
- 智能指针扩展 RAII → 统一所有权管理
🧠 九、RAII vs GC 思维对比
| 特性 | RAII (C++) | GC (Java/Python) |
|---|---|---|
| 资源释放时机 | 作用域结束 | 不确定(由 GC 决定) |
| 控制性 | 精确可控 | 不可预测 |
| 性能 | 高效、确定性 | 延迟、不确定 |
| 适用场景 | 系统、游戏、实时程序 | 脚本、Web、工具开发 |
“RAII 是确定性的垃圾回收。”
🧪 十、手写智能指针(简化版)
📦 演示实现
template<typename T>
class UniquePtr {
public:
explicit UniquePtr(T* ptr = nullptr) : data(ptr) {}
~UniquePtr() { delete data; }
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
UniquePtr(UniquePtr&& other) noexcept : data(other.data) {
other.data = nullptr;
}
UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
delete data;
data = other.data;
other.data = nullptr;
}
return *this;
}
T* operator->() const { return data; }
T& operator*() const { return *data; }
private:
T* data;
};📦 使用:
UniquePtr<int> up(new int(7));
std::cout << *up; // 输出 7
🧠 十一、记忆口诀
| 口诀 | 含义 |
|---|---|
| 🧩 “资源绑定生命周期” | 构造即获取,析构即释放 |
| ⚙️ “不要手动 delete” | 智能指针接管释放 |
| 📦 “make 而非 new” | 安全创建对象 |
| 🧠 “unique 表独占,shared 表共享” | 清晰的所有权语义 |
| 🪄 “weak 打破循环引用” | 防止 shared_ptr 死锁 |
✅ 十二、实践建议
- ✅ 优先使用
std::make_unique/std::make_shared - ✅ 避免裸指针拥有资源(可用作观察者但不负责释放)
- ✅ 仅在必要时使用
std::weak_ptr(例如缓存或图) - ✅ RAII 结构应无副作用、无手动清理代码
- ✅ 对象的构造应即完成资源获取
🪄 十三、工程哲学
“RAII 是 C++ 的灵魂;智能指针是 RAII 的延续。”
RAII 并非仅用于内存,它适用于:
- 文件句柄 (
std::fstream) - 锁 (
std::lock_guard) - 网络连接(Socket)
- 线程 (
std::thread::join) - 数据库事务(commit/rollback)
每个资源都应“随作用域而生,随作用域而灭”。
📚 十四、拓展阅读
- 📘 Effective Modern C++ — Scott Meyers
- 📗 The C++ Programming Language (4th Ed.) — Bjarne Stroustrup
- 📙 More Effective C++ — Scott Meyers
- 🔗 Herb Sutter: GotW - Guru of the Week
- 🧠 CppReference: RAII
🎯 总结一句话
“RAII 是让 C++ 可控又安全的根基,
智能指针是 RAII 的现代武器。” 🧭