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|异常安全


“在 C++ 中,RAII 是最安全的资源管理模式。

析构函数自动释放资源,智能指针则将 RAII 泛化为通用所有权模型。”


📦 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 化管理

std::mutex mtx;

void safeOperation() {
    std::lock_guard<std::mutex> guard(mtx);  // 加锁
    // 临界区代码
} // guard 离开作用域 → 自动解锁

🧠 要点:

  • std::lock_guard 是 RAII 的经典封装。
  • 构造时加锁,析构时解锁。
  • 避免手动 mtx.lock() / mtx.unlock() 的遗忘。

void foo() {
    int* p = new int(42);
    // 忘记 delete?→ 内存泄漏!
    // 多次 delete?→ 未定义行为!
}

🧠 RAII 的解决方案:

→ “让对象的析构函数自动调用 delete。”


📦 定义与使用

#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);  // ✅ 推荐写法

📦 示例

#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 打破循环引用。


📦 示例

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 不参与引用计数
};

🧠 讲解:

  • weak_ptr 不增加引用计数
  • 可通过 .lock() 安全访问底层对象(若已销毁则返回空)
  • 常用于双向链表、图结构、缓存系统等

📦 推荐用法

auto p1 = std::make_unique<MyClass>(args...);
auto p2 = std::make_shared<MyClass>(args...);

🧠 优势:

  1. 避免两次内存分配(尤其是 shared_ptr
  2. 更安全:不易引发异常泄漏
  3. 更简洁:避免 new

📦 等价手动写法(❌ 不推荐)

std::shared_ptr<MyClass> p(new MyClass(args...)); // 可能泄漏

📦 传统方式的问题

Widget* w = new Widget();
doSomething(); // 可能抛异常
delete w;      // 永远不会执行

📦 RAII 改进

auto w = std::make_unique<Widget>();
doSomething();  // 即使抛异常也安全释放

🧠 总结:

  • RAII 自动调用析构 → 防止资源泄漏
  • 智能指针扩展 RAII → 统一所有权管理

特性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 的现代武器。” 🧭