Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

内存管理 / Memory Management

1. C++ 内存管理总览:先看完整地图 / C++ Memory Management Map

2. 程序内存布局:内存不是只有堆和栈 / Program Memory Layout

3. 栈与函数调用:局部变量为什么会消失 / Stack and Function Calls

4. 堆与动态分配:new 到底做了什么 / Heap and Dynamic Allocation

5. 对象生命周期:地址还在不代表对象还活着 / Object Lifetime

6. RAII 与所有权:让对象负责清理 / RAII and Ownership Design

7. 智能指针与所有权关系:谁拥有,谁只是看 / Smart Pointers and Ownership Graphs

8. 容器、迭代器与内存失效 / Containers, Iterators, and Invalidation

9. allocator 与 PMR:把分配策略抽出来 / Allocators and PMR

10. 内存错误与 Sanitizer:把崩溃变成证据 / Memory Errors and Sanitizers

本页目录

RAII 与所有权:让对象负责清理 / RAII and Ownership Design ​

RAII 不是高级术语,也不是“析构会自动释放”一句话。

它是 C++ 管理资源的中心思想:让资源的获得和释放绑定到对象生命周期。

1. 资源不只是内存 ​

资源是程序需要申请、用完必须归还的东西。

常见资源包括:

text
动态内存
文件句柄
互斥锁
网络连接
数据库连接
线程
临时文件
GPU 资源
1
2
3
4
5
6
7
8

如果资源没有正确释放,后果可能是:

text
内存泄漏
文件一直被占用
锁没有释放导致死锁
连接耗尽
数据没有刷新到磁盘
1
2
3
4
5

所以 RAII 不只是内存技巧。

它是 C++ 的资源管理方式。

2. 没有 RAII 的代码为什么脆弱 ​

想象一个手动管理资源的流程:

text
open resource
  |
  v
step 1
  |
  v
step 2
  |
  +-- return early
  +-- throw exception
  +-- goto error path
  |
  v
close resource
1
2
3
4
5
6
7
8
9
10
11
12
13
14

只要中途路径多起来,释放逻辑就容易漏。

代码可能写成:

cpp
void work() {
    Resource* r = acquire();

    if (!step1(r)) {
        release(r);
        return;
    }

    if (!step2(r)) {
        release(r);
        return;
    }

    release(r);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

看起来还能忍。

但如果中间抛异常,或者新增第三、第四个出口,就很容易出问题。

3. RAII 的基本形状 ​

RAII 类型的基本形状是:

cpp
class Owner {
public:
    Owner() {
        // acquire resource
    }

    ~Owner() {
        // release resource
    }
};
1
2
3
4
5
6
7
8
9
10

流程变成:

text
构造对象
  |
  v
获得资源
  |
  v
使用对象
  |
  +-- 正常离开作用域
  +-- 提前 return
  +-- 异常导致栈展开
  |
  v
析构函数自动释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14

一个最熟悉的例子是文件流:

cpp
#include <fstream>
#include <iostream>
#include <string>

void print_file(const char* path) {
    std::ifstream input(path);

    if (!input) {
        std::cerr << "cannot open file\n";
        return;
    }

    std::string line;
    while (std::getline(input, line)) {
        std::cout << line << '\n';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

input 离开作用域时会析构。

文件资源会被关闭。

你不需要在每个 return 前手写 close()。

4. 所有权要能从类型看出来 ​

裸指针看不出所有权:

cpp
void use(Widget* p);
1

这个接口可能表示:

text
只是临时使用,不负责 delete
接管所有权,函数内部会 delete
可能为空
保存起来以后再用
1
2
3
4

这些语义如果只靠注释,很容易被误用。

更好的方式是让类型表达:

cpp
void observe(const Widget& widget);
void maybe_observe(const Widget* widget);
void take(std::unique_ptr<Widget> widget);
std::unique_ptr<Widget> make_widget();
1
2
3
4

含义更清楚:

text
const Widget&
  借用一个必然存在的对象,不拥有

const Widget*
  借用一个可能为空的对象,不拥有

std::unique_ptr<Widget>
  独占拥有,传入表示交出所有权

返回 std::unique_ptr<Widget>
  函数创建对象,并把所有权交给调用者
1
2
3
4
5
6
7
8
9
10
11

5. RAII 和异常 ​

异常发生时,普通语句会被跳过。

但已经构造成功的局部对象会按规则析构。

cpp
void work() {
    std::unique_ptr<Widget> w = std::make_unique<Widget>();

    step1(*w);
    step2(*w); // 如果这里抛异常
    step3(*w);
}
1
2
3
4
5
6
7

如果 step2 抛异常:

text
step2 throws
  |
  v
work() 不能继续正常执行
  |
  v
开始栈展开
  |
  v
w 析构
  |
  v
Widget 被释放
1
2
3
4
5
6
7
8
9
10
11
12
13

这就是 C++ 特别依赖 RAII 的原因。

它让清理逻辑不依赖每条路径都手写。

6. move-only owner ​

有些资源不能复制所有权。

比如一个独占文件句柄、一个独占堆对象。

如果复制 owner,就会有两个对象都以为自己负责释放同一资源。

所以 RAII owner 经常是只可移动、不可复制。

std::unique_ptr 就是这样:

cpp
#include <memory>

auto a = std::make_unique<int>(42);
// auto b = a;            // 不允许复制
auto b = std::move(a);    // 允许移动所有权
1
2
3
4
5

移动后:

text
before
  a owns int

after std::move
  b owns int
  a no longer owns it
1
2
3
4
5
6

这不是为了麻烦你。

这是为了防止重复释放。

7. 本篇总结 ​

RAII 的核心不是一句英文缩写。

它的工作方式是:

text
资源
  |
  v
交给对象拥有
  |
  v
对象构造时获得资源
  |
  v
对象析构时释放资源
  |
  v
复制/移动规则表达所有权如何转移
1
2
3
4
5
6
7
8
9
10
11
12
13

设计接口时,优先让类型说清楚:

text
谁拥有?
谁借用?
能否为空?
能否共享?
能否跨作用域保存?
1
2
3
4
5

下一篇讲智能指针。智能指针不是“自动 delete 的指针”,而是几种不同所有权关系的类型表达。

最后更新于:

Pager
上一篇5. 对象生命周期:地址还在不代表对象还活着 / Object Lifetime
下一篇7. 智能指针与所有权关系:谁拥有,谁只是看 / Smart Pointers and Ownership Graphs

持续记录,持续成长

Copyright © Tidenflow