在现代C++编程中,资源管理的核心原则是 RAII(Resource Acquisition Is Initialization)。RAII 的核心思想是:资源(如内存、文件句柄、锁等)在对象构造时获取,在对象析构时自动释放。智能指针(std::unique_ptr、std::shared_ptr、std::weak_ptr)正是利用 RAII 的机制来实现内存安全与异常安全的关键工具。
1. RAII 的基本模式
class FileHandle {
public:
explicit FileHandle(const char* path)
: fp(fopen(path, "r")) {
if (!fp) throw std::runtime_error("Cannot open file");
}
~FileHandle() {
if (fp) fclose(fp);
}
FILE* get() const { return fp; }
private:
FILE* fp;
};
FileHandle 在构造时打开文件,在析构时关闭文件。即使函数中出现异常,FileHandle 的析构函数也会被调用,确保文件句柄被正确释放。
2. 智能指针的实现细节
2.1 std::unique_ptr
std::unique_ptr 是独占所有权的指针。其实现主要依赖模板析构函数 ~unique_ptr(),在对象销毁时调用自定义删除器 Deleter,默认是 `std::default_delete
`。
“`cpp
template<class t class deleter="std::default_delete>
class unique_ptr {
public:
explicit unique_ptr(T* ptr = nullptr) noexcept : ptr_(ptr) {}
~unique_ptr() { if (ptr_) std::default_delete
()(ptr_); }
// 访问
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
// 转移所有权
unique_ptr(unique_ptr&& other) noexcept : ptr_(other.release()) {}
unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) {
reset(other.release());
}
return *this;
}
// 禁止拷贝
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
private:
T* ptr_;
};
“`
当 `unique_ptr` 超出作用域时,析构函数会自动删除所持有的对象,确保不泄漏。
### 2.2 `std::shared_ptr`
`std::shared_ptr` 通过引用计数实现共享所有权。引用计数维护在一个控制块中,包含计数器和删除器。计数器采用原子操作以支持多线程安全。
“`cpp
class control_block {
public:
std::atomic
use_count{1};
std::atomic
weak_count{0};
void* ptr;
Deleter deleter;
};
“`
每次 `shared_ptr` 复制时,`use_count` 加 1;析构时减 1;当计数为 0 时,调用删除器销毁对象;若随后 `weak_count` 也为 0,则删除控制块。
## 3. RAII 与异常安全
RAII 的最大优势之一是异常安全。考虑下面的函数:
“`cpp
void process() {
std::unique_ptr data(new int[100]); // RAII
// … 进行一些操作
throw std::runtime_error(“boom”); // 异常抛出
}
“`
即使异常被抛出,`data` 的析构函数会被自动调用,释放数组,避免内存泄漏。相比之下,裸指针需要手动写 `delete[]`,很容易遗漏。
## 4. 组合使用智能指针与其他资源
### 4.1 文件句柄与 `unique_ptr`
“`cpp
struct FileCloser {
void operator()(FILE* fp) const noexcept {
if (fp) fclose(fp);
}
};
void readFile(const char* path) {
std::unique_ptr file(fopen(path, “r”));
if (!file) throw std::runtime_error(“Cannot open file”);
// 读取操作…
}
“`
使用自定义删除器,使 `unique_ptr` 也能管理非内存资源。
### 4.2 线程锁与 `unique_lock`
“`cpp
std::mutex mtx;
void safe_increment(int& counter) {
std::unique_lock lock(mtx); // RAII
++counter; // 线程安全
}
“`
`std::unique_lock` 的构造获取锁,析构释放锁,保证了在异常或提前返回时锁仍会被释放。
## 5. 常见陷阱与注意事项
1. **循环引用**
`shared_ptr` 与 `weak_ptr` 的组合可以避免循环引用。若在类内部互相持有 `shared_ptr`,会导致计数永远不为 0,造成内存泄漏。
2. **自定义删除器与控制块**
`unique_ptr` 的自定义删除器在析构时会被调用,但其生命周期仅限于 `unique_ptr` 对象;如果需要持久化状态,建议使用 `shared_ptr` 或 `std::shared_ptr` 的 `enable_shared_from_this`。
3. **裸指针与智能指针混用**
只要可能,避免裸指针悬挂。若需要与第三方 API 交互,使用 `std::unique_ptr` 的 `release()` 或 `get()` 返回裸指针,但务必确保所有权不会被错误转移。
4. **多线程与原子计数**
`shared_ptr` 的引用计数是原子的,支持多线程并发拷贝和销毁,但内部对象本身不具备线程安全,仍需自行加锁。
## 6. 小结
RAII 与智能指针是 C++ 现代编程的两大支柱。它们通过对象生命周期与资源绑定,天然支持异常安全与多线程安全。熟练掌握 `std::unique_ptr`、`std::shared_ptr`、`std::weak_ptr` 与自定义删除器的使用模式,能够显著提升代码的健壮性与可维护性。祝你在 C++ 开发旅程中愉快地使用 RAII 与智能指针,构建安全高效的软件系统!