
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 << "world!\n");
DEFER(std::cout << "Hello, ");
// 输出: Hello, world!
}⚠️ 注意:std::function 有轻微开销;若追求零成本抽象,可改用模板参数捕获(见 gsl::finally 实现)。
✅ 方式二:std::shared_ptr<void> 技巧(无依赖、一行式)
利用 shared_ptr 的自定义删除器,在析构时触发回调:
立即学习“C++免费学习笔记(深入)”;
#include <memory>
#include <iostream>
int main() {
// 构造一个空指针 shared_ptr,删除器即 defer 动作
auto _ = std::shared_ptr<void>(nullptr,
[](...) { std::cout << ", World!\n"; });
std::cout << "Hello";
}此法无需额外类定义,但语义稍隐晦,且 ... 参数仅用于抑制警告(C++17 起可省略)。
✅ 方式三:采用成熟库实现(生产环境首选)
-
gsl::finally(来自 Microsoft GSL):
#include <gsl/gsl_util> int main() { auto cleanup = gsl::finally([]{ std::cout << "cleanup\n"; }); // ... 可能抛异常或提前 return }底层为模板化函数对象,零运行时开销,且支持完美转发。
标准提案参考: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 真正想教会我们的事。


















