核心内容摘要
Java实战:OpenCSV与Apache Commons CSV高效解析CSV数据对比
作用域守卫Scope Guard让清理代码乖乖在“出门顺手关灯”那一刻执行写嵌入式代码时总会遇到这样的人生真相你在函数某处申请了资源打开外设、上锁、禁中断、分配缓冲……后来代码分叉、提前return、甚至抛出异常——结果忘了释放/恢复。
结果就是内存泄漏、死锁、外设状态奇怪或者你被老大盯着问“为什么这段代码跑了两分钟还没返回”。
作用域守卫Scope Guard就是为了解决这个问题的——把“离开当前作用域时必须做的事”绑定在一个对象的析构函数上只要对象离开作用域析构函数就会执行清理也就稳了。
它是 RAII 的小而美的实用变体尤其适合嵌入式场景没有堆分配、追求确定性。
先看最简单的lambda 小模板这是最常见的现代 C 写法C11 起就能用。
核心思想封装一个可调用对象析构时调用它如果没有被取消。
#includeutility#includeexception#includecstdlib// for std::terminatetemplatetypenameFclassScopeGuard{public:explicitScopeGuard(Ff)noexcept:fn_(std::move(f)),active_(true){}// 不允许拷贝避免重复调用ScopeGuard(constScopeGuard)delete;ScopeGuardoperator(constScopeGuard)delete;// 允许移动ScopeGuard(ScopeGuardother)noexcept:fn_(std::move(other.fn_)),active_(other.active_){other.dismiss();}~ScopeGuard()noexcept{if(active_){try{fn_();}catch(...){// 析构函数不可抛出 —— 在嵌入式中通常直接终止std::terminate();}}}voiddismiss()noexcept{active_false;}private:F fn_;boolactive_;};// 辅助函数方便模板推导templatetypenameFScopeGuardFmake_scope_guard(Ff){returnScopeGuardF(std::forwardF(f));}用法示例voidfoo(){autogmake_scope_guard([](){close_device();});// do something...if(error)return;// close_device 会被保证调用g.dismiss();// 如果想提前取消清理}幽默注dismiss()就是给守卫放假不让它在离职那天烦你。
成功/失败分支scope_success和scope_fail有时候你只想在函数“正常返回”no exception时做事或者只在抛异常时处理。
C17 提供了std::uncaught_exceptions()来判断析构时是否处于异常传播中。
基于它我们可以实现scope_exit总是执行、scope_success仅在没有异常时执行、scope_fail仅在有异常时执行。
#includeexceptiontemplatetypenameFclassScopeGuardOnExit{// 同上始终执行};templatetypenameFclassScopeGuardOnSuccess{public:explicitScopeGuardOnSuccess(Ff)noexcept:fn_(std::move(f)),active_(true),uncaught_at_construction_(std::uncaught_exceptions()){}~ScopeGuardOnSuccess()noexcept{if(active_std::uncaught_exceptions()uncaught_at_construction_){try{fn_();}catch(...){std::terminate();}}}// ... move/dismiss same as aboveprivate:F fn_;boolactive_;intuncaught_at_construction_;};templatetypenameFclassScopeGuardOnFail{public:explicitScopeGuardOnFail(Ff)noexcept:fn_(std::move(f)),active_(true),uncaught_at_construction_(std::uncaught_exceptions()){}~ScopeGuardOnFail()noexcept{if(active_std::uncaught_exceptions()uncaught_at_construction_){try{fn_();}catch(...){std::terminate();}}}// ...private:F fn_;boolactive_;intuncaught_at_construction_;};这样你就可以写autoon_successmake_scope_guard_success([](){commit_tx();});autoon_failmake_scope_guard_fail([](){rollback_tx();});在嵌入式里如果禁用异常这俩就没用武之地 —— 但是scope_exit总是执行仍然非常有用。
方便的宏减少样板代码写守卫变量名挺烦的宏可以帮你#defineCONCAT_IMPL(x,y)x##y#defineCONCAT(x,y)CONCAT_IMPL(x,y)#defineSCOPE_GUARD(code)\autoCONCAT(_scope_guard_,__COUNTER__)make_scope_guard([](){code;})用法SCOPE_GUARD({disable_irq();restore_irq_state(saved);});// 作用域结束时自动调用在没有__COUNTER__的编译器上用__LINE__也行不过__COUNTER__更保险。
例子禁用中断并保证恢复伪代码平台提供__disable_irq()/__enable_irq()voidcritical_section(){boolprevsave_and_disable_irq();autorestoremake_scope_guard([]{restore_irq(prev);});// 关键操作}上锁/解锁mutex.lock();autounlockmake_scope_guard([]{mutex.unlock();});// 如果函数中途 returnmutex 会被正确解锁临时改变寄存器、并在退出恢复uint32_toldREG_CTRL;REG_CTRLold|ENABLE_BIT;autorestore_regmake_scope_guard([]{REG_CTRLold;});嵌入式
注意事项与最佳实践不要分配堆内存守卫对象本身应该在栈上成员不要包含动态分配嵌入式通常禁用或不喜欢堆。
析构函数必须不抛异常标准要求析构不能抛出会导致std::terminate在异常传播时。
我们的实现用try/catch(...) { std::terminate(); }或者如果你有日志系统可以先记录再终止。
另一个选择是静默吞掉异常但那可能掩盖错误。
尽量内联inline模板 constexpr/inline有利于编译器优化不增加运行时开销。
对象大小实现非常小一个可调用对象 一个bool对内存敏感的场景适合。
避免把大对象捕获到 lambda 中。
编译器/标准如果你用的是老旧编译器确保至少支持 C11lambda、移动语义。
若要scope_success/fail需要 C17 的std::uncaught_exceptions()。
禁用异常的环境如果工程编译时禁用异常-fno-exceptionsscope_fail/scope_success不可用。
scope_exit仍然适用仍能保证清理行为。
避免在中断上下文做复杂事情在 ISR 中创建守卫要小心栈空间有限、不要调用可能阻塞或分配的函数。
用std::unique_ptr的小技巧简单场景有时候你只是想用现有工具来做简单清理#includememoryautocloserstd::unique_ptrvoid,decltype([](void*){close_fd(fd);})(nullptr,[](void*){close_fd(fd);});但这种写法语义上不如专门的ScopeGuard清晰模板更适合做任意清理我提是为了给你多一个“武器库”里的小工具。