---
title: "C++中RAII的一些思考"
date: 2026-09-01
category: "C++"
tags: ["C++", "类", "RAII"]
summary: "引言\\n\\n在学习智能指针之前，我总听说RAII是c++资源管理的核心思想，但一直停留在字面印象上。直到自己动手写了一个管理int的RAII类，把资源的申请和释放绑进构造与析构里，才算是真正体会到它的妙处。\\n\\n在此篇文章中，我将记录下自己对RAII的理解，以供思考，如有不足之处欢迎指正。…"
series:
  name: "C++特性及用法"
  order: 0
---

## 引言

在学习智能指针之前，我总听说RAII是c++资源管理的核心思想，但一直停留在字面印象上。直到自己动手写了一个管理int的RAII类，把资源的申请和释放绑进构造与析构里，才算是真正体会到它的妙处。

RAII即Resource Acquisition Is Initialization，资源的获取就是初始化。

在此篇文章中，我将记录下自己对RAII的理解，以供思考，如有不足之处欢迎指正。

## 正文

### 构造即获取，析构即释放

RAII的类自己持有资源，本例中就是一块堆上的int：

```cpp
class RAII_int_t {
private:
    int *m_int_p{};//持有的资源指针，默认初始化为nullptr

public:
    RAII_int_t()
    {
        m_int_p = new int{};//构造时申请资源
    }

    explicit RAII_int_t(const int &value)
    {
        m_int_p = new int{value};
    }

    ~RAII_int_t()
    {
        delete m_int_p;//析构时释放资源
    }
};
```

***资源在构造函数中获取，在析构函数中释放，资源的生命周期与对象的生命周期完全绑定。***

这么看来，只要对象按正常的路径离开作用域，`delete`就一定会被执行，`main`里那段代码没有一个`delete`，却也没有任何泄漏。

> 用在上一篇文章里写的生命周期视角来看，RAII其实是把栈对象的确定性析构，变成了管理堆资源的工具。

### 深拷贝的拷贝构造

资源类的拷贝不能只复制指针，否则两个对象析构时会对同一块内存`delete`两次：

```cpp
RAII_int_t(const RAII_int_t &other)
{
    m_int_p = new int{other.get()};//重新new一块，做深拷贝
}
```

每个对象持有自己独立的那份int，互相之间的修改不会影响对方。

### copy and swap的赋值

赋值运算符这里我用了copy and swap的写法：

```cpp
RAII_int_t &operator=(RAII_int_t other)//注意参数是按值传入
{
    std::swap(m_int_p, other.m_int_p);//交换指针
    return *this;
}
```

按值传参意味着实参先被**拷贝构造**成一个临时对象，然后我们把自己手里的指针和临时对象交换，函数结束时临时对象析构，顺带把我们原来的旧资源释放掉了。

我认为这个写法非常巧妙：拷贝、交换、释放三件事，只用一个函数和一次析构就完成了，而且天然自赋值安全，异常安全也有保证。

> 对比手动版本的赋值`operator=(const int &value)`，那里要先new再delete旧指针，顺序一旦写反就丢了资源，而copy and swap把这份风险交给了拷贝构造和析构函数。

### 与int的互转

为了让这个类用起来像一个int，我加了类型转换运算符：

```cpp
operator int() const { return *m_int_p; };//隐式转换为int

[[nodiscard]] int get() const { return *m_int_p; };
```

有了`operator int()`，`add(t1, t3)`这类接受int的函数可以直接传入RAII对象，隐式转换自动完成。

而`get()`标记了`[[nodiscard]]`，忘记使用返回值时编译器会给出警告。

> 隐式转换方便但也危险，标准库的智能指针就特意用`explicit`修饰了bool转换，防止意外转换。这里为了实验的流畅性保留了隐式版本。

### 补充要点

三个构造函数都处理了创建路径：默认构造new一个零值int，带值构造按值创建，接受指针的构造则做了一层判空兜底：

```cpp
explicit RAII_int_t(const int *value_p)
{
    if (value_p != nullptr) {
        m_int_p = new int{*value_p};//拷贝指针指向的值
    }
    else {
        m_int_p = new int{};//无效指针退化为默认构造
    }
}
```

注意这里即使传入指针也是重新`new`一份，和拷贝构造一样遵循"每个对象独占一份资源"的原则。

## 小结

RAII的本质是把资源的生命周期托管给栈上对象的构造与析构，只要对象离开作用域，资源就必然被释放。写完这个类再回头看智能指针，就会发现unique_ptr不过是把同样的思想做得更完善罢了。

用一个小小的int体会一遍资源管理的套路，之后面对文件、内存这些真正的资源时，思路是相通的。

本文所用的测试代码收录于[Learncpp](https://github.com/ManJin03/Learncpp)，如有不妥之处欢迎指正。
