保证C++内存安全的核心RAII思想是什么?RAII思想及智能指针介绍
RAII(资源获取即初始化)是C++特有的资源管理思想,通过将资源生命周期与对象生命周期绑定,利用C++自动调用析构函数的特性实现资源自动管理。核心原理是在构造函数中获取资源,在析构函数中释放资源,确保资源随对象销毁自动释放。智能指针是RAII的典型应用:unique_ptr独占资源,不可拷贝;shared_ptr通过引用计数共享资源;weak_ptr解决循环引用问题。RAII彻底避免了资源泄漏,
RAII思想
RAII思想的核心定义
RAII(Resource Acquisition Is Initialization)直译为 “资源获取即初始化”,使用局部对象来管理资源的技术称为资源获取即初始化;这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入;是 C++ 特有的、基于对象生命周期的资源管理思想,核心目标是:
- 将资源的生命周期与对象的生命周期绑定;
- 利用 C++ 自动调用析构函数的特性,实现 “资源自动申请、自动释放”,彻底避免资源泄漏。
RAII 的两个关键前提
- 栈对象的生命周期:栈上的局部对象(比如函数里定义的普通变量),只要离开作用域(比如函数执行完、
{}代码块结束、抛出异常),编译器会强制自动调用它的析构函数,这是 C++ 语言层面的保障; - 资源绑定:把需要管理的资源(比如堆内存、文件)封装到这个类里,让「对象生→资源生,对象死→资源死」。
RAII 的核心原理
C++ 中,栈上的对象(局部对象)有一个关键特性:当对象离开作用域时,编译器会自动调用其析构函数(无论正常退出还是异常退出)。
RAII 正是利用这一特性,将资源管理封装到类中:
- 资源获取(初始化):在类的构造函数中申请资源(如
new内存、fopen打开文件); - 资源持有:对象生命周期内,通过类的成员函数提供资源访问接口;
- 资源释放:在类的析构函数中释放资源(如
delete内存、fclose关闭文件); - 自动执行:对象离开作用域时,析构函数自动调用,资源被无条件释放 —— 即使程序抛出异常,栈展开过程中也会销毁局部对象,析构函数依然会执行。
可视化RAII执行流程
RAII 是 C++ 的 “资源管家”—— 对象创建时 “接手” 资源,对象销毁时 “交还” 资源,全程自动,无需人工干预。
智能指针
智能指针是栈上的 RAII 对象(而非 “栈上的指针”),这个对象内部持有指向堆内存的裸指针;当智能指针对象离开作用域时,其析构函数会自动调用 delete 释放它管理的堆内存 —— 无论代码正常执行还是抛出异常,释放操作都会执行,从而彻底防止内存泄漏。
C++ 标准库已经提供了成熟的 RAII 实现 ——智能指针,这是 RAII 最核心、最常用的应用,主要有两种:
| 智能指针 | 核心特点(RAII 体现) | 适用场景 |
|---|---|---|
std::unique_ptr |
独占式管理资源:一个指针只能管一个资源,不能拷贝,析构时自动 delete |
绝大多数场景 |
std::shared_ptr |
共享式管理资源:引用计数,计数为 0 时才 delete,支持拷贝 |
需要多个指针共享一个资源 |
unique_ptr
#include <iostream>
#include <memory> // 智能指针的头文件
#include <stdexcept>
using namespace std;
void func()
{
// 1. 创建 unique_ptr(RAII 方式管理堆内存)
unique_ptr<int> p(new int(20));
// 或更安全的写法:make_unique(C++14 及以上)
// auto p = make_unique<int>(20);
// 2. 访问资源(和普通指针一样)
cout << *p << endl;
// 3. 模拟异常:资源依然会释放
throw runtime_error("测试异常");
}
int main() {
try {
func();
} catch (const exception& e) {
cout << e.what() << endl;
}
// 无需 delete,p 析构时自动释放内存
return 0;
}
关键特性:
unique_ptr不能拷贝(unique_ptr<int> p2 = p;会编译报错),避免浅拷贝重复释放;- 支持 移动语义(
std::move),可以转移资源所有权(比如函数返回unique_ptr); - 体积和普通指针一样,无额外性能开销。
shared_ptr
class Ball
{
public:
Ball() { cout << "A ball has been created." << endl; }
~Ball() { cout << "A ball has been destroyed." << endl; }
void bounce() { cout << "The ball is bouncing!" << endl; }
};
int main()
{
shared_ptr<Ball> p = make_shared<Ball>();
cout << p.use_count() << endl; // 1
shared_ptr<Ball> p2 = p;
cout << p.use_count() << ' ' << p2.use_count() << endl;// 2 2
shared_ptr<Ball> p3 = p2;
cout << p.use_count() << ' ' << p2.use_count() << ' ' << p3.use_count() << endl;// 3 3 3
// The ball is bouncing!
p->bounce();
p2->bounce();
p3->bounce();
// reset the pointer one by one
p.reset();
cout << p2.use_count() << ' ' << p3.use_count() << ' ' << endl; // 2 2
p2.reset();
cout << p3.use_count() << endl; // 1
p3.reset();
// Auto deleted Ball : "A ball has been destroyed"
return 0;
}
关键特性:
- 内部维护「引用计数」,每拷贝一次计数 +1,每销毁一个指针计数 -1;
- 只有计数为 0 时,才会调用
delete释放资源; - 注意:避免「循环引用」(比如两个
shared_ptr互相指向对方),否则计数永远不为 0,会导致内存泄漏
weak_ptr
为了解决上面的shared_ptr循环引用问题,C++还引入了weak_ptr:
weak_ptr 是 C++ 标准库提供的弱引用智能指针,核心特点:
- 它是
shared_ptr的 “辅助工具”,不能单独使用(无法直接访问堆内存); - 持有
weak_ptr不会增加shared_ptr的引用计数(“弱引用” 的含义); - 可以随时从
weak_ptr转换成shared_ptr,从而安全访问堆内存。
class B;
class A {
public:
weak_ptr<B> b_ptr; // 改为 weak_ptr(弱引用)
~A() { cout << "A 析构" << endl; }
};
class B {
public:
weak_ptr<A> a_ptr; // 改为 weak_ptr(弱引用)
~B() { cout << "B 析构" << endl; }
};
int main() {
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->b_ptr = b; // weak_ptr 接收 shared_ptr,计数不增加
b->a_ptr = a; // weak_ptr 接收 shared_ptr,计数不增加
// 查看引用计数:都是 1(关键!)
cout << "a 的计数:" << a.use_count() << endl; // 1
cout << "b 的计数:" << b.use_count() << endl; // 1
} // main 结束,a/b 销毁,计数减到 0,析构函数执行!
核心变化:
- A 和 B 中不再持有
shared_ptr,而是weak_ptr; weak_ptr接收shared_ptr时,不会增加引用计数(计数始终为 1);- main 结束后,a/b 销毁,计数减到 0,A 和 B 的析构函数执行,堆内存正常释放。
weak_ptr 的核心操作
weak_ptr 不能直接解引用(*wp 会报错),必须先转换成 shared_ptr 才能访问资源,核心操作有 3 个:
| 操作 | 作用 |
|---|---|
wp.lock() |
将 weak_ptr 转换成 shared_ptr:1. 若资源还存在 → 返回有效的 shared_ptr;2. 若资源已释放 → 返回空的 shared_ptr |
wp.expired() |
判断所指向的资源是否已释放(计数为 0):true = 已释放,false = 未释放 |
wp.use_count() |
获取对应的 shared_ptr 的引用计数(和 shared_ptr 的 use_count 一致) |
int main()
{
// 1. 创建 shared_ptr
shared_ptr<int> sp = make_shared<int>(100);
// 2. 创建 weak_ptr,关联到 sp
weak_ptr<int> wp = sp;
// 3. 查看状态
cout << "是否过期:" << boolalpha << wp.expired() << endl; // false
cout << "计数:" << wp.use_count() << endl; // 1
// 4. 转换成 shared_ptr 访问资源
/*
shared_ptr 类重载了布尔类型转换运算符(operator bool())—— 这个运算符会让 shared_ptr 对象在需要 bool 值的场景(比如 if 条件)下,自动转换成 bool:
如果 shared_ptr 持有有效资源(指针非空)→ 转换成 true;
如果 shared_ptr 是空的(指针为空,资源已释放)→ 转换成 false。
*/
if (shared_ptr<int> temp = wp.lock()) { // 资源未过期,temp 有效
cout << *temp << endl; // 输出 100
} else {
cout << "资源已释放" << endl;
}
// 5. 释放 sp,资源销毁
sp.reset(); // 手动重置 shared_ptr,计数减到 0
cout << "是否过期:" << wp.expired() << endl; // true
// 6. 再次尝试访问(失败)
if (shared_ptr<int> temp = wp.lock()) {
cout << *temp << endl;
} else {
cout << "资源已释放" << endl; // 输出这行
}
return 0;
}
weak_ptr 的核心特性
- 不增加引用计数:这是和
shared_ptr最核心的区别,也是解决循环引用的关键; - 不管理资源生命周期:
weak_ptr不会触发资源释放,只有shared_ptr的计数为 0 时才会释放; - 线程安全(基础层面):
weak_ptr的lock()操作是原子的,多线程下转换不会出问题; - 不能单独使用:必须依赖
shared_ptr才能创建,无法直接make_weak_ptr。
更多推荐



所有评论(0)