第3章:资源管理(Resource Management)
一、导读
1.1 本章学习目标
第3章讲解 C++ 中资源管理的核心原则:RAII(Resource Acquisition Is Initialization)。通过对象的生命周期管理资源,确保资源在对象销毁时自动释放。
本章核心学习目标:
- 掌握 RAII 的核心思想:资源获取即初始化
- 理解智能指针的使用:unique_ptr、shared_ptr、weak_ptr
- 掌握资源泄漏的预防方法:如何确保 new/delete 配对
- 理解克隆(clone)方法的使用场景
1.2 为什么本章如此重要
资源泄漏是 C++ 中最常见也最危险的错误之一。RAII 和智能指针是解决这一问题的核心工具。
1.3 核心问题引导
RAII 如何保证异常安全?
unique_ptr 和 shared_ptr 的选择标准是什么?
为什么应该用智能指针而非裸指针?
二、核心概念详解
2.1 RAII 原则
cpp
// 不好: 手动管理资源
void processFile() {
FILE* fp = fopen("data.txt", "r");
if (!fp) return;
char* buffer = new char[1024];
// ... 处理文件 ...
if (some_error) return; // 忘记释放 buffer!
delete[] buffer;
fclose(fp);
}
// 好: RAII 自动管理
void processFileRAII() {
std::ifstream file("data.txt"); // 构造时打开
if (!file) return;
std::vector<char> buffer(1024); // 自动管理内存
// ... 处理文件 ...
if (some_error) return; // 自动清理!
} // file 和 buffer 自动析构
// RAII 的核心: 资源的生命周期 = 对象的生命周期
// 构造获取资源,析构释放资源2.2 智能指针
cpp
// unique_ptr: 独占所有权
{
auto ptr = std::make_unique<Widget>(42); // C++14
ptr->doSomething();
} // 自动 delete
// shared_ptr: 共享所有权,引用计数
{
auto sp1 = std::make_shared<Widget>(42);
{
auto sp2 = sp1; // 引用计数 = 2
} // sp2 析构,引用计数 = 1
} // sp1 析构,引用计数 = 0,delete Widget
// weak_ptr: 不增加引用计数,解决循环引用
class Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 弱引用,不阻止析构
};
// 使用场景:
// unique_ptr: 工厂函数返回值、独占资源
// shared_ptr: 共享缓存、观察者模式
// weak_ptr: 缓存、打破循环引用2.3 防止资源泄漏
cpp
// 不好: 容易忘记释放
Widget* createWidget() {
return new Widget(42);
}
void useWidget() {
Widget* w = createWidget();
w->doSomething();
delete w; // 容易忘记
}
// 好: 返回智能指针
std::unique_ptr<Widget> createWidgetSafe() {
return std::make_unique<Widget>(42);
}
void useWidgetSafe() {
auto w = createWidgetSafe();
w->doSomething();
} // 自动释放三、实战案例
3.1 案例一:自定义 RAII 包装器
cpp
// 用 unique_ptr 管理 C 风格资源
struct FileCloser {
void operator()(FILE* fp) const {
if (fp) fclose(fp);
}
};
using UniqueFile = std::unique_ptr<FILE, FileCloser>;
UniqueFile openFile(const char* path, const char* mode) {
return UniqueFile(fopen(path, mode));
}
void processFile() {
auto file = openFile("data.txt", "r");
if (!file) throw std::runtime_error("Cannot open file");
char buffer[1024];
size_t n = fread(buffer, 1, sizeof(buffer), file.get());
// ... 处理数据 ...
} // file 自动关闭
// 互斥锁的 RAII 包装
class MutexGuard {
std::mutex& mtx;
public:
explicit MutexGuard(std::mutex& m) : mtx(m) { mtx.lock(); }
~MutexGuard() { mtx.unlock(); }
MutexGuard(const MutexGuard&) = delete;
MutexGuard& operator=(const MutexGuard&) = delete;
};
void threadSafeFunction() {
static std::mutex mtx;
MutexGuard guard(mtx); // 构造时加锁
// ... 临界区 ...
} // 析构时自动解锁四、本章小结
RAII 通过对象生命周期管理资源,是 C++ 资源管理的核心原则
unique_ptr 用于独占所有权,shared_ptr 用于共享所有权
weak_ptr 用于打破循环引用,不增加引用计数
优先使用智能指针而非裸指针,避免资源泄漏
五、思考与练习
思考题
shared_ptr 的引用计数为什么需要原子操作?
为什么工厂函数应该返回 unique_ptr 而非裸指针?
编程练习
实现一个 RAII 的数据库连接包装器。
实现一个简单的 shared_ptr(包含引用计数)。