RAII惯用法:面试官问“你怎么保证资源不泄漏”,我说new/delete配对然后被教育了 阶段一C基础完结从今天开始进入阶段二C进阶与实战。先聊一个贯穿整个C资源管理的核心思想——RAIIResource Acquisition Is Initialization。面试的时候面试官问你怎么保证资源不泄漏我说new和delete配对使用。面试官摇摇头说如果中间抛异常了呢如果函数有多个return呢我当时没答上来。后来才明白RAII才是C管理资源的正解。RAII的核心思想RAII的全称是资源获取即初始化。说白了就是把资源的生命周期绑定到对象的生命周期上。构造函数里获取资源析构函数里释放资源。C保证局部对象的析构函数一定会被调用——不管是正常退出、提前return、还是异常导致的栈展开。// 不用RAII容易泄漏 void processFile() { FILE* f fopen(data.txt, r); if (!f) return; char* buffer new char[1024]; if (!readData(f, buffer)) { // 忘了delete buffer就return了——泄漏 return; } delete[] buffer; fclose(f); } // 用RAII不可能泄漏 void processFile() { std::ifstream file(data.txt); // 构造时打开文件 if (!file) return; std::vectorchar buffer(1024); // 构造时分配内存 file.read(buffer.data(), 1024); // file和buffer的析构函数自动释放资源 // 不管函数怎么退出都不会泄漏 }这就是RAII的威力。你不需要手动管理资源对象的生命周期就是资源的生命周期。RAII不只是管理内存很多人以为RAII只管内存。其实RAII可以管理任何资源文件句柄、互斥锁、网络socket、数据库连接、硬件设备。之前讲过的lock_guard就是RAII的典型应用std::mutex mtx; void criticalSection() { std::lock_guardstd::mutex lock(mtx); // 构造时加锁 // ... 访问共享数据 // 析构时自动解锁不管怎么退出 }在机器人开发里RAII的应用场景更多// 硬件设备的RAII封装 class LidarHandle { int fd_; public: LidarHandle(const char* device) { fd_ open(device, O_RDWR); // 构造时打开设备 if (fd_ 0) throw std::runtime_error(Cannot open lidar); } ~LidarHandle() { if (fd_ 0) close(fd_); // 析构时关闭设备 } // 禁止拷贝 LidarHandle(const LidarHandle) delete; LidarHandle operator(const LidarHandle) delete; }; // 使用 void readLidar() { LidarHandle lidar(/dev/ttyUSB0); // 使用lidar读取数据 // 函数退出时自动关闭设备 // 即使抛异常也不会忘记close }自定义RAII包装器有时候标准库没有你需要的RAII类型需要自己写。一个通用的模式是用unique_ptr加自定义删除器// 用unique_ptr包装C语言的资源 auto fileDeleter [](FILE* f) { if (f) fclose(f); }; std::unique_ptrFILE, decltype(fileDeleter) file( fopen(data.txt, r), fileDeleter); // 包装OpenCV的Mat虽然OpenCV自己已经管理了 // 包装FFmpeg的上下文 auto ctxDeleter [](AVFormatContext* ctx) { avformat_close_input(ctx); }; std::unique_ptrAVFormatContext, decltype(ctxDeleter) fmtCtx( nullptr, ctxDeleter);这种写法的好处是不需要定义新类一行代码就能包装任何C语言的资源。更正式的做法是写一个RAII类class ScopedTimer { std::string label_; std::chrono::steady_clock::time_point start_; public: ScopedTimer(const std::string label) : label_(label), start_(std::chrono::steady_clock::now()) {} ~ScopedTimer() { auto end std::chrono::steady_clock::now(); auto ms std::chrono::duration_caststd::chrono::milliseconds( end - start_).count(); std::cout label_ took ms ms endl; } }; // 使用测量代码块的执行时间 void processFrame() { ScopedTimer timer(processFrame); // ... 处理一帧数据 // 函数退出时自动打印耗时 }这个ScopedTimer在机器人开发里特别实用。调试性能问题的时候在关键函数里加一行ScopedTimer就能知道每个模块花了多少时间。面试中的RAII考点RAII的核心原理是什么利用C的栈展开机制——局部对象的析构函数在离开作用域时一定会被调用。把资源释放放在析构函数里就保证了资源一定会被释放。RAII和垃圾回收有什么区别垃圾回收如Java的GC是运行时自动回收不再使用的内存但不确定性——你不知道GC什么时候运行。RAII是确定性的——对象离开作用域就立刻释放时间点是确定的。对于硬件设备、文件句柄这种需要及时释放的资源RAII比GC更可靠。如果析构函数抛出异常会怎样这是C的大忌。析构函数不应该抛异常。如果析构函数在栈展开过程中抛异常程序会直接terminate。所以RAII类的析构函数要做异常安全处理——要么catch住异常要么标记为noexcept。再聊一个面试中经常被追问的点RAII在机器人硬件资源管理中的应用。除了内存机器人开发里还有很多需要RAII管理的资源——串口连接、Socket通信、GPU显存、DMA通道等等。我之前做一个激光雷达驱动时用RAII封装了串口资源构造函数打开串口并配置波特率析构函数关闭串口释放文件描述符。这样即使初始化过程中抛出异常串口也一定会被正确关闭。面试时如果你能举出这种非内存资源的RAII应用案例面试官会觉得你不只是理解概念而是真正在项目中实践过。另外还有个技巧用std::unique_ptr配合自定义删除器可以给C语言的资源句柄加上RAII语义比如std::unique_ptrFILE, decltype(fclose)来管理文件指针。给正在准备面试的你RAII是C最重要的惯用法之一没有之一。它把资源管理和对象生命周期绑定在一起从根本上杜绝了资源泄漏。面试的时候能讲清楚RAII的原理再举出lock_guard、ifstream、unique_ptr这些标准库的例子说明你理解了C的设计哲学。如果能结合项目经验自己写一个RAII包装器给面试官看那面试官会觉得你的C功底相当扎实。下篇聊Rule of Three/Five/Zero——类设计的黄金法则和RAII有着非常直接的关系。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第40篇 C并发编程入门——thread、mutex、atomic与机器人多线程场景 下一篇预告第42篇 Rule of Three/Five/Zero——类设计的黄金法则有任何问题欢迎评论区留言我会尽量回复。