
C++ 虽无原生 defer 语句,但可通过 RAII 机制(如作用域守卫类、std::shared_ptr 自定义删除器或 gsl::finally)模拟其“函数退出时自动执行”的行为,兼顾异常安全与资源确定性清理。
c++ 虽无原生 `defer` 语句,但可通过 raii 机制(如作用域守卫类、`std::shared_ptr` 自定义删除器或 `gsl::finally`)模拟其“函数退出时自动执行”的行为,兼顾异常安全与资源确定性清理。
Go 的 defer 提供了一种简洁、可读性强的延迟执行机制:将清理逻辑(如关闭文件、释放锁、记录日志)与资源获取紧邻书写,由编译器保证其在函数返回(含异常退出)前按后进先出(LIFO)顺序执行。C++ 标准虽未内置该特性,但凭借确定性的栈对象析构顺序和 RAII 原则,可高效、安全地实现等效语义。
推荐实现方式
✅ 方式一:轻量级作用域守卫类(推荐自用)
您提供的 FrameExitTask 思路正确,但需修正关键缺陷:应支持移动语义以避免误删,且需确保 lambda 捕获安全。以下是优化版本:
#include <functional>
#include <utility>
class defer_guard {
std::function<void> f_;
public:
explicit defer_guard(std::function<void> f) : f_(std::move(f)) {}
defer_guard(defer_guard&&) noexcept = default;
defer_guard& operator=(defer_guard&&) noexcept = default;
defer_guard(const defer_guard&) = delete;
defer_guard& operator=(const defer_guard&) = delete;
~defer_guard() { if (f_) f_(); }
};
// 使用宏简化语法(可选,注意作用域)
#define DEFER(...) defer_guard CONCAT(_defer_, __LINE__){[&](){ __VA_ARGS__; }};
#define CONCAT(a, b) a##b
int main() {
DEFER(std::cout <blockquote><p>⚠️ 注意:std::function 有轻微开销;若追求零成本抽象,可改用模板参数捕获(见 gsl::finally 实现)。</p></blockquote>
<h4>✅ 方式二:std::shared_ptr<void> 技巧(无依赖、一行式)</void>
</h4>
<p>利用 shared_ptr 的自定义删除器,在析构时触发回调:</p>
<pre class="brush:php;toolbar:false;">#include <memory>
#include <iostream>
int main() {
// 构造一个空指针 shared_ptr,删除器即 defer 动作
auto _ = std::shared_ptr<void>(nullptr,
[](...) { std::cout <p>此法无需额外类定义,但语义稍隐晦,且 ... 参数仅用于抑制警告(C++17 起可省略)。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件"><img
src="https://img.php.cn/upload/webcode/000/000/164/636a2b4d84031727.png" alt="使用Go语言搭建家庭相册系统-相关课件" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件" class="overflowclass">使用Go语言搭建家庭相册系统-相关课件</a>
<p class="overflowclass">使用Go语言搭建家庭相册系统-相关课件</p>
</div>
<a rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<h4>✅ 方式三:采用成熟库实现(生产环境首选)</h4>
<ul>
<li>
<p><strong>gsl::finally</strong>(来自 <a href="https://www.php.cn/link/36a2d857e8c4d30b525eadb5040940cb" rel="nofollow" target="_blank">Microsoft GSL</a>):</p>
<pre class="brush:php;toolbar:false;">#include <gsl>
int main() {
auto cleanup = gsl::finally([]{ std::cout <p>底层为模板化函数对象,零运行时开销,且支持完美转发。</p></gsl>
标准提案参考:N3830/P0052 提案推动的 scope_exit 等类型,已有多个开源实现(如 scope17),语义更明确。
关键注意事项
- ✅ 异常安全:所有 RAII 方案均天然支持异常传播,析构函数在栈展开(stack unwinding)期间被调用。
- ⚠️ 信号不安全:defer 类机制依赖正常控制流(return/异常),无法响应 SIGKILL 等强制终止信号。
- ❌ 禁止跨作用域转移:defer_guard 对象必须声明于目标作用域内,不可通过指针/引用逃逸,否则析构时机失控。
- ? 执行顺序:与 Go 一致,按声明逆序执行(LIFO),符合直觉。
总结
C++ 的 defer 模拟不是权宜之计,而是 RAII 设计哲学的自然延伸。对于新项目,建议直接集成 gsl::finally 或 C++20 后兼容的 scope_exit;若需最小依赖,手写带移动语义的守卫类是清晰、可控的选择。核心原则始终不变:将资源生命周期绑定到作用域,而非手动管理——这正是 defer 真正想教会我们的事。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










