raii是c++中不可替代的资源管理契约,通过构造函数获取、析构函数释放资源,将生命周期绑定作用域;裸指针易致泄漏或悬空,应优先使用std::make_unique/std::make_shared,避免手动new/delete及循环引用。

RAII不是语法糖,是C++里唯一靠谱的资源管理契约
RAII(Resource Acquisition Is Initialization)不是可选技巧,而是C++中内存不泄漏、对象不悬空的前提。它靠构造函数获取资源、析构函数释放资源,把生命周期绑定到作用域上——只要对象能被正确销毁,资源就一定被释放。
关键点在于:你不能绕过它去“手动管理”,否则delete漏掉一次,或者异常中途跳出作用域,delete就永远没机会执行。
- 所有裸指针(
int*、MyClass*)都不该在栈上长期持有,除非你明确知道它指向的是静态/全局/已托管内存 -
new配delete、new[]配delete[]必须严格一一对应;混用会触发未定义行为,常见表现为double free or corruption - 函数返回裸指针?基本等于把资源管理责任甩给调用方——而调用方大概率不知道该不该
delete
std::unique_ptr 是最常用也最容易写错的智能指针
std::unique_ptr本质是“独占语义”的栈上对象,它自己不分配堆内存,只管理别人分配的堆内存。它的析构函数自动调用delete(或delete[]),但前提是构造时传入了合法指针。
常见错误现象:std::unique_ptr<int> p(new int(42));</int>看起来没问题,但如果new抛异常(比如内存耗尽),p根本不会被构造,new分配的内存就泄露了——这正是std::make_unique存在的理由。
立即学习“C++免费学习笔记(深入)”;
- 永远优先用
std::make_unique<t>(...)</t>,而不是std::unique_ptr<t>(new T(...))</t> - 想传递所有权?用
std::move(p),之后p变成nullptr;直接赋值或拷贝会编译报错(copy constructor is deleted) - 数组要用
std::unique_ptr<int></int>,且必须用std::make_unique<int>(n)</int>,不能用new int[n]配普通unique_ptr<int></int>
std::shared_ptr 的循环引用不是玄学,是引用计数模型的必然结果
std::shared_ptr靠引用计数管理生命周期,当计数归零才释放资源。但它无法识别两个shared_ptr互相持有对方——比如A对象里存着指向B的std::shared_ptr<b></b>,B里又存着指向A的std::shared_ptr<a></a>,那么即使A和B都离开作用域,它们的引用计数也永远不会降到0。
典型表现:程序退出前内存没释放,Valgrind报告“still reachable”块,shared_ptr析构函数压根没被调用。
- 类内部需要反向引用外部对象时,用
std::weak_ptr替代std::shared_ptr,访问前调用lock()检查是否还有效 -
std::shared_ptr的控制块(control block)额外分配内存,比unique_ptr有轻微开销;频繁拷贝shared_ptr本身成本低,但大量创建/销毁会触发控制块分配 - 不要用
shared_ptr管理栈对象或全局对象(比如std::shared_ptr<int>(&x)</int>),析构时会delete栈地址,直接崩溃
自定义类型必须显式实现移动语义才能安全参与RAII
如果你写了带std::unique_ptr成员的类,却没声明移动构造函数和移动赋值运算符,编译器生成的默认版本会尝试拷贝unique_ptr——这会导致编译失败(因为unique_ptr不可拷贝)。更糟的是,如果成员是裸指针,而你没写移动操作,编译器会生成浅拷贝,两个对象指向同一块内存,析构时double free。
一个没写移动语义的类,放进std::vector扩容时可能崩溃,传参时可能意外复制,返回局部对象时可能被强制拷贝(C++11前)或静默失败(C++11后)。
- 只要类里有资源(裸指针、文件句柄、socket等),就必须考虑移动语义:用
= default让编译器生成,或手写并确保转移后源对象处于有效但未定义状态 - 禁用拷贝:把拷贝构造函数和拷贝赋值设为
= delete,避免误用 - 析构函数里别抛异常——RAII的清理阶段一旦抛异常,程序直接
std::terminate
RAII的边界其实很窄:它只管“对象生命周期结束时该做什么”,不管“什么时候该结束”。决定生命周期的,是你把对象放在哪——栈上?容器里?还是shared_ptr堆上?选错位置,再严谨的RAII也救不了。










