行行宜行行

热爱风光、热爱新技术、踏出每一步,过好每一天!

使用 RAII 实现 C++ 的 defer 机制

简介

在 C++ 中,资源管理(如内存、文件句柄、锁等)是一项关键任务。现代 C++ 提倡使用 RAII(资源获取即初始化) 方式来自动管理资源生命周期,避免手动释放导致的资源泄漏、异常不安全等问题。

本文实现了一个通用的 defer 机制,类似于 Go 中的 defer,用于在作用域退出时自动执行某段代码,以提升代码的安全性与可读性,主要可以用于在一些项目中在作用域退出时做资源的释放,我个人的日志项目中也使用了这种方法,故想记录一下基本原理。


核心思想:RAII(Resource Acquisition Is Initialization)

RAII 是 C++ 的一个编程习惯,其核心是将资源的生命周期绑定到对象生命周期上:

  • 获取资源 → 构造对象
  • 释放资源 → 析构对象

一旦对象离开作用域,其析构函数就会被调用,自动清理资源。

这意味着:

{
    std::lock_guard<std::mutex> lock(mtx); /// 构造时加锁
    /// 临界区代码
} /// 自动释放锁(调用析构)

同样地,在项目中可以借助 RAII 实现 defer 逻辑。


实现原理:ExecuteOnScopeExit

类定义

class ExecuteOnScopeExit {
public:
    ExecuteOnScopeExit() = default;
    ExecuteOnScopeExit(ExecuteOnScopeExit&&) = default;
    ExecuteOnScopeExit& operator=(ExecuteOnScopeExit&&) = default;

    ExecuteOnScopeExit(const ExecuteOnScopeExit&) = delete;
    ExecuteOnScopeExit& operator=(const ExecuteOnScopeExit&) = delete;

    template <typename F, typename... Args>
    ExecuteOnScopeExit(F&& f, Args&&... args) {
        func_ = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
    }

    ~ExecuteOnScopeExit() noexcept {
        if (func_) func_();
    }

private:
    std::function<void()> func_;
};

核心逻辑

  • 构造时绑定任意可调用对象(函数、lambda)
  • 析构时调用它
  • 禁止拷贝,支持移动(防止副本干扰作用域生命周期)

宏定义

C++ 没有内建的 defer,需要使用宏封装自动构造匿名对象:

#define _LOG_CONCAT(a, b) a##b
#define _MAKE_DEFER_(line) logger::ExecuteOnScopeExit _LOG_CONCAT(defer, line) = [&]()
#define LOG_DEFER _MAKE_DEFER_(__LINE__)

宏展开示例

LOG_DEFER {
    std::cout << "defer: end of scope!\n";
}

相当于:

logger::ExecuteOnScopeExit defer42 = [&]() {
    std::cout << "defer: end of scope!\n";
};

使用示例

基本使用

void example() {
    LOG_DEFER {
        /// 在离开整个作用域时才会调用
        std::cout << "scope exited!\n";
    };

    std::cout << "inside scope\n";
}

输出:

inside scope
scope exited!

多个 defer(后进先出 LIFO)

LOG_DEFER { std::cout << "3\n"; };
LOG_DEFER { std::cout << "2\n"; };
LOG_DEFER { std::cout << "1\n"; };

输出:

1
2
3

异常安全保证

void test() {
    LOG_DEFER {
        std::cout << "cleanup even if exception\n";
    };

    throw std::runtime_error("error");
}

即使发生异常,defer 注册的清理操作也会执行。


注意事项

  • LOG_DEFER 生成的变量名依赖 __LINE__,每个 defer 必须单独一行声明
  • defer 语句后必须以 ; 结尾
  • 作用范围限定在当前作用域,不能跨函数、跨语句块

与其他语言的 defer 对比

语言关键字特性
Godefer编译器内建
RustDropTrait-based RAII
C++RAII + 宏需要自己构建
Pythonwith上下文管理器

总结

通过 RAII 和宏组合,C++ 模拟 defer 机制,自动的清理资源、提升异常安全性,并保持代码简洁清晰。

这种模式特别适用于:

  • 文件句柄、锁、线程等资源自动释放
  • 调试和性能分析钩子
  • 保证函数返回前执行特定操作
Previous Article
Next Article

发表回复

Your email address will not be published. Required fields are marked *.

*
*

近期评论

您尚未收到任何评论。

conviction

要想看到璀璨的日出时刻,就要有跋山涉水的决心和坚毅