如何在函数出口上运行清理代码?

2023-12-11

C++ 类提供 RAII 习惯用法。因此你不必关心异常:

void function()
{
    // The memory will be freed automatically on function exit
    std::vector<int> vector(1000);

    // Do some work        
}

但是,如果您(由于某些原因)必须使用某些纯 C API,则必须围绕它创建 C++ 包装器或使用 try/catch 块

void function()
{
    int *arr = (int*)malloc(1000*sizeof(int));
    if (!arr) { throw "cannot malloc"; }

    try
    {
        // Do some work
    }
    catch (...)
    {
        free(arr); // Free memory in case of exception
        throw;     // Rethrow the exception
    }

    // Free memory in case of success
    free(arr);
}

即使您使用具有 RAII 习惯用法的 C++ 类,有时您也必须编写具有强大异常安全保证的代码:

void function(std::vector<const char*> &vector)
{
    vector.push_back("hello");
    try
    {
        // Do some work

        vector.push_back("world");
        try
        {
            // Do other work
        }
        catch (...)
        {
            vector.pop_back(); // Undo vector.push_back("world")
            throw;             // Rethrow the exception
        }
    }
    catch (...)
    {
        vector.pop_back(); // Undo vector.push_back("hello");
        throw;             // Rethrow the exception
    }
}

但这些结构相当庞大。

有没有办法强制在函数退出时运行一些清理代码?类似的东西atexit,但是在函数范围内...

有没有办法在发生异常时运行一些回滚代码而不使用嵌套的 try/catch 块?

我想要一些像这样工作的运算符或函数:

void function(std::vector<const char*> &vector)
{
    int *arr = malloc(1000*sizeof(int));
    onexit { free(arr); }

    vector.push_back("hello");
    onexception { vector.pop_back(); }

    // Do some work

    vector.push_back("world");
    onexception { vector.pop_back(); }

    // Do other work
}

如果可以创建此类函数,是否有任何理由避免使用它们?其他编程语言中是否有这样的结构?


我创建了实现此功能的宏。它们生成一个局部变量,该变量使用 C++11 lambda 函数在析构函数中运行清理代码。这std::uncaught_exception函数用于检查当前是否有异常抛出。创建变量本身不应引发任何异常,因为使用包含通过引用捕获的所有变量的 lambda 来创建变量(此类 lambda 不会在复制/移动构造函数中引发异常)。

#include <exception>

// An object of the class below will run an arbitrary code in its destructor
template <bool always, typename TCallable>
class OnBlockExit
{
public:
    TCallable m_on_exit_handler;

    ~OnBlockExit()
    {
        if (always || std::uncaught_exception())
            { m_on_exit_handler(); }
    }
};

// It is not possible to instantiate an object of the 'OnBlockExit' class
// without using the function below: https://stackoverflow.com/a/32280985/5447906.
// Creating of an object of the 'OnBlockExit' class shouldn't throw any exception,
// if lambda with all variables captured by reference is used as the parameter.
template <bool always, typename TCallable>
OnBlockExit<always, TCallable> MakeOnBlockExit(TCallable &&on_exit_handler)
{
    return { std::forward<TCallable>(on_exit_handler) };
}

// COMBINE is needed for generating an unique variable
// (the name of the variable contains the line number:
// https://stackoverflow.com/a/10379844/544790)
#define COMBINE1(X,Y) X##Y
#define COMBINE(X,Y) COMBINE1(X,Y)

// ON_BLOCK_EXIT generates a variable with the name
// in the format on_block_exit##__LINE__
#define ON_BLOCK_EXIT(always, code) \
    auto COMBINE(on_block_exit,__LINE__) = MakeOnBlockExit<always>([&]()code)

// Below are target macros that execute the 'code' on the function exit.
// ON_FINALLY will allways execute the code on the function exit,
// ON_EXCEPTION will execute it only in the case of exception.
#define ON_EXCEPTION(code) ON_BLOCK_EXIT(false, code)
#define ON_FINALLY(code)   ON_BLOCK_EXIT(true , code)

以下是如何使用这些宏的示例:

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    auto file = fopen("file.txt", "rb");
    if (!file) { throw "cannot open file.txt"; }
    ON_FINALLY({ fclose(file); });

    vector.push_back("bye");
    ON_EXCEPTION({ vector.pop_back(); });

    int *arr3 = (int*)malloc(1000*sizeof(int));
    if (!arr3) { throw "cannot malloc arr3"; }
    ON_FINALLY({ free(arr3); });

    arr1[1] = 1;
    arr2[2] = 2;
    arr3[3] = 3;
}

所有清理代码都以相反的顺序执行(与ON_FINALLY/ON_EXCEPTION宏出现在函数中)。仅当控制超出相应的范围时才会执行清理代码ON_FINALLY/ON_EXCEPTION macro.

检查以下链接以查看演示程序执行的输出:http://coliru.stacked-crooked.com/a/d6defaed0949dcc8

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在函数出口上运行清理代码? 的相关文章

  • ASP.NET 会员电子邮件验证

    尝试基于 C 创建电子邮件验证本文 https web archive org web 20211020153319 https www 4guysfromrolla com articles 062508 1 aspx 我创建了一个 ja
  • 沿着长数据序列在固定大小的移动窗口中查找中值

    给定一个数据序列 可能有重复项 一个固定大小的移动 窗口 从数据开始处每次迭代时移动窗口 序列 使得 1 从窗口中删除最旧的数据元素并添加新数据 元素被推入窗口 2 求每次移动时窗口内数据的中位数 以下帖子没有帮助 有效地找到随机序列的中值
  • 为什么不能使用initializer_list来初始化unique_ptr的向量? [复制]

    这个问题在这里已经有答案了 我想知道为什么initializer list 不能与unique ptr 一起使用 std vector
  • Windows CE 6.0 和运行时链接到调试 DLL /MDd

    我在 x86 PC 上使用 Windows CE 6 0 R3 我已经为该平台构建了 NK bin 和 SDK 但我有一些问题需要了解如何使用 MTd 调试 DLL 构建控制台应用程序 如果我尝试构建这个 main c with MDd i
  • MSVC10 /MP 在项目中跨文件夹构建非多核

    我希望有人指出我们所遇到的错误或解决方法 使用 MP 编译项目时 似乎仅同时编译同一文件夹中的文件 我使用进程资源管理器来滑动命令行并确认行为 项目过滤器似乎对同时编译的内容没有影响 项目结构disk Folder project vcxp
  • 使用 pthread_cond_signal 优雅地终止线程被证明是有问题的

    我需要发射一堆线程 并希望优雅地将它们拉下来 我正在尝试使用pthread cond signal pthread cond wait实现这一目标 但遇到了问题 这是我的代码 首先是thread main static void thrma
  • 如何(可移植地)在 C 和 C++ 中获取 DBL_EPSILON

    我正在 Linux AS 3 上使用 GCC 3 4 并试图找出DBL EPSILON 或者至少是一个不错的近似值 我怎样才能以编程方式获取它 在 C 中是std numeric limits
  • 为什么 .Net 框架指南建议您不要使用 ref/out 参数?

    显然 他们很 混乱 这是认真的原因吗 你还能想到其他的吗 你见过有多少开发人员并不真正理解 ref out 吗 我在真正需要的地方使用它们 但在其他地方则不然 它们通常仅在您想有效返回两个或多个值时才有用 在这种情况下它至少值得thinki
  • 在 C++ 中初始化指针

    可以在声明时将指针分配给值吗 像这样的东西 int p 1000 是的 您可以在声明时初始化指向值的指针 但是您不能这样做 int p 1000 是个地址运算符 并且您不能将其应用于常量 尽管如果可以 那会很有趣 尝试使用另一个变量 int
  • Docker 不遵循构建目录中的符号链接

    我正在对一个应用程序进行 Docker 化 其中涉及通过 Clang 将二进制文件与其他 C 文件链接 我们维护二进制文件的符号链接版本 因为它们在整个代码库中使用 我的 Docker 构建目录包含整个代码库 包括源文件以及这些源文件的符号
  • C++ 虚拟关键字与重写函数

    我正在学习c 并且正在学习virtual关键字 我在互联网上搜索试图理解它但无济于事 我进入编辑器并做了以下实验 期望它打印两次基本消息 因为我的印象是需要 virtual 关键字来覆盖函数 然而 它打印出了两条不同的消息 有人可以向我解释
  • Ajax 函数在重定向后不保存滚动位置

    正如标题所述 我编写了一个 ajax 函数 该函数应该滚动到用户在重定向之前所在的位置 我写了一个alert对于测试场景 它确实触发了 但滚动不断回到顶部 我在这里做错了什么 JavaScript ajax type GET url Adm
  • DataContractJsonSerializer 包含元素类型子类型的通用列表

    我要使用DataContractJsonSerializer用于 JSON 序列化 反序列化 我在 JSON 数组中有两种对象类型 并希望将它们都反序列化为相应的对象类型 具有以下类定义 DataContract public class
  • 不可能的事情发生了!这是什么意思?

    我遇到了一个有趣的运行时错误 我认为这是某种内存泄漏 我写了以下程序 C Code include
  • 更改成员资格、角色等的默认连接字符串

    默认情况下 我的网络应用程序似乎正在使用LocalSqlServer作为用于任何应用程序服务 例如成员资格 角色 身份验证 等 的连接字符串 有什么方法可以更改默认连接字符串应该是什么 默认值是 LocalSqlServer 似乎很随意 我
  • 使用 Node.js 访问用 C++ 编写的 SDK

    我有一个用 C 语言编写的 SDK 可以与我的扫描仪设备进行通信 我需要开发一个可以访问扫描仪设备的电子应用程序 我知道有很多库可用于扫描仪 但我想使用这个 SDK 因为它允许我访问设备的完整功能 而且它是由设备制造商提供的 那么 有没有什
  • C 中函数“fgets”的参数太少

    每当我编译这个错误时 我都会收到该错误 但我不知道为什么 我直接从书上抄袭这个 有人可以帮忙吗 include
  • 不兼容的指针到字符转换

    我正在编写一个程序 将卡片值写入 52 个点字符的多维数组中 该程序是一个测试数组 稍后我将其作为函数写入主程序中 在程序中 我通过以下方式初始化 for 循环计数0通过51 我用一个switch语句调制13将卡牌值分配给数组点 但是 我收
  • 如何在您的网站中连接两个人

    有一款名为 Verbosity 的游戏 这是一款有目的的游戏 位于此链接上www gwap com 在游戏中 他们随机连接两个玩家互相玩 游戏是玩家1应该向他的搭档 玩家2 描述一个单词 而玩家2应该猜测这个单词 我正在尝试建立一个网站来执
  • 使用 Crypto++ 和 .NET 的 CFB 模式下的 TripleDES

    我正在尝试使用 TripleDES 使用 C 应用程序获得相同的结果 该应用程序具有Crypto https www cryptopp com 和 NET应用程序使用三重DESCryptoServiceProvider https msdn

随机推荐

  • 具有非 None 类属性的类的新实例?

    我有一个 Python 类 其类属性设置为除None 创建新实例时 对该属性所做的更改将在所有实例中永久存在 下面是一些代码来解释这一点 class Foo object a b 2 foo Foo foo a append item fo
  • 在 OptionMenu 选择更改后更新标签文本

    我的目标是更新标签的内容price 每次选项菜单中出现新项目时w被选中 到目前为止 这是我的代码 但它返回了我不知道如何修复的错误 class App Frame def init self master None Frame init s
  • 使用 Symfony2 和 Doctrine2 插入另一个表后尝试更新一个表

    我写了一个函数BudgetRepository将新数据插入预算表时调用该函数 其功能是 public function addBudgetToClient clientId budgetId return this gt createQue
  • 用于处理应用程序内弹出窗口的 Web 浏览器

    我正在尝试使用 WebBrowser 控件启动弹出窗口的新表单 而不是在 IE 中打开它 我尝试使用 AxWebBrowser 来获取与 NewWindow3 事件一起使用的弹出窗口 并且只是做e ppDisp AxWebBrowser A
  • 将 pandas 数据框字符串条目拆分(分解)为单独的行

    我有一个pandas dataframe其中一列文本字符串包含逗号分隔的值 我想拆分每个 CSV 字段并为每个条目创建一个新行 假设 CSV 是干净的并且只需要按 进行拆分 例如 a应该成为b In 7 a Out 7 var1 var2
  • Intellij Scala 工作表运行类型差异解释

    在 Intellij Scala 工作表支持中 运行类型 即 PLAIN 与 REPL 之间有什么区别 Plain评估模型在评估表达式之前一次性编译整个工作表 同时REPL评估模型在移动到下一个之前评估每个表达式 添加一个表达式REPL模式
  • JS Ajax调用PHP并获取ajax调用数据

    我有一个标准的 javascript ajax 调用 我将 data 设置为 json 数据 ajax type POST url BaseUrl User Login url BaseUrl User Limit 1 2 data api
  • 数组上“大于”(以及另一个不等式比较运算符)的行为

    我找不到任何描述或任何提及如何 gt lt lt and gt 在 JavaScript 中比较两个数组时运算符的行为 我能想到的唯一一件微不足道的事情是 两个数组通过每个相对索引的两个元素进行比较 但在测试之后 我没有得到我预期的结果 那
  • 将“分组”和“堆叠”组合在条形图中?

    我知道如何画一个groupedBAR 图和stackedBAR图如下 Y round rand 5 4 10 figure subplot 2 2 1 bar Y grouped title Group similar for hist h
  • 收到 ERR_INVALID_HTTP_RESPONSE 错误 laravel5 本地主机

    当我启动我的程序时我陷入困境拉拉维尔 5本地主机上的项目谷歌浏览器 我正在使用此存储库中的引导管理面板 https github com start laravel sb admin laravel 5 我安装了所有的需求 比如npm bo
  • sendTextMessage(电话号码, null, 消息, null, null);即使消息未发送,也始终返回成功

    我尝试使用短信活动这个链接但问题是它总是给出 消息已发送 如何知道消息是否真的发送了 这是代码 try SmsManager smsManager SmsManager getDefault smsManager sendTextMessa
  • IIS Express 过度缓存经典 ASP 页面

    我遇到了这个随机出现的问题 当我对 ASP 页面进行更改时 IIS Express 无法识别 它提供页面的先前版本 我可以通过重新编辑页面 进行一些小的更改 然后撤消更改并重新保存文件来解决此问题 但这很烦人 到目前为止 我只在经典 ASP
  • 在oracle SQL中计算没有日历表的工作日(包括日期之间的假期)

    好的 所以我已经阅读了大量关于在 sql 中模拟 excel 的网络日函数的可能性的文章 并得出结论 到目前为止最简单的解决方案是有一个日历表 该表将标记工作日或非工作日工作日 然而 由于我无法控制的情况 我们无法享受到如此奢侈的享受 而且
  • 如何使用 JavaScript 检测地址栏更改?

    我有一个 Ajax 密集型应用程序 可能有一个 URL 例如 http example com myApp page 1 当用户操作该网站时 地址栏可能会更改为类似的内容 http example com myApp page 5 无需重新
  • 从 floatbuffer 转换为 byte[]

    我正在尝试找到一种在java中使用jack audio的方法 我已经成功创建基于 jnajack 的包装器 以将音频从 jacks 的端口获取到 java 应用程序 原始 jnajack 不适用于 jack 1 9 8 但我找不到操作数据的
  • IE10 中等高列内的元素内容不是 100%

    对于我正在开发的应用程序 我需要等高的列 我选择使用 CSS 将我的列项目设置为表格的样式 这样每根柱子的高度确实是柱子高度中最大的 请参阅此处的示例 http jsfiddle net roelvd GXe9m 现在每个浏览器中每列的高度
  • 浮点处理器非确定性?

    在不涉及不必要的细节的情况下 基于相同的输入 浮点数 x86 64 上的运算是否有可能返回其结果的微小变化 哪怕有一点点不同 我正在模拟一个基本混沌的系统 我希望数据的微小变化会产生明显的影响 然而我预计 使用相同的数据 程序的行为将被修复
  • 在Python子进程中,使用Popen()和check_output()有什么区别?

    以shell命令 cat file txt 为例 使用 Popen 可以运行 import subprocess task subprocess Popen cat file txt shell True stdout subprocess
  • 访问闭包捕获的变量

    我想知道是否有任何方法可以从函数外部访问函数中闭包捕获的变量 例如如果我有 A function b var c function some code using b foo function do things with c 有什么办法可
  • 如何在函数出口上运行清理代码?

    C 类提供 RAII 习惯用法 因此你不必关心异常 void function The memory will be freed automatically on function exit std vector