避免重复C错误处理

Aus*_*ser 5 c error-handling gcc c-preprocessor

我经常编写代码,这些代码最终都是长序列

int error;

error = do_something();
if (error) {
    return error;
}

error = do_something_else(with, some, args);
if (error) {
    return error;
}


error = do_something_yet_again();
if (error) {
    return error;
}

return 0;
Run Code Online (Sandbox Code Playgroud)

我正在寻找一种更清晰的方式来写这个,这在某种程度上避免了重复的相同检查.到目前为止,我已经编写了一个ERROR_OR宏,它的工作方式类似于

#define ERROR_OR(origerr, newerr)           \
    ({                                      \
        int __error_or_origerr = (origerr); \
        (__error_or_origerr != 0)           \
                ? __error_or_origerr        \
                : (newerr);                 \
    })
Run Code Online (Sandbox Code Playgroud)

这允许原始代码变得像

int error = 0;

error = ERROR_OR(error, do_something());
error = ERROR_OR(error, do_something_else(with, some, args));
error = ERROR_OR(error, do_something_yet_again());

return error;
Run Code Online (Sandbox Code Playgroud)

这(在我看来)有点清洁.它也不太容易理解,因为ERROR_PRESERVE除非你阅读它的文档和/或实现,否则宏的功能并不明显.它也没有解决重复的问题,只是更容易在一行上编写所有(现在隐式)检查.

我真的想重写这一切,如下所示:

return ERROR_SHORT_CIRCUIT(
    do_something(),
    do_something_else(with, some, args),
    do_something_yet_again()
);
Run Code Online (Sandbox Code Playgroud)

假设的ERROR_SHORT_CIRCUIT宏会

  • 在其参数列表中获取可变数量的表达式
  • 按顺序评估每个表达式
  • 如果每个表达式的计算结果为零,则评估为零
  • 如果任何表达式求值为非零,则立即终止并求值为该最后一个表达式的值

最后一个条件是我的短路偏离了||操作员的直接使用- 因为这将评估为1而不是误差值.

我最初尝试写这个是以下内容:

#define ERROR_SHORT_CIRCUIT(firsterr, ...)          \
    ({                                              \
        int __error_ss_firsterr = (firsterr);       \
        (__error_ss_firsterr != ERROR_NONE)         \
                ? __error_ss_firsterr               \
                : ERROR_SHORT_CIRCUIT(__VA_ARGS__); \
    })
Run Code Online (Sandbox Code Playgroud)

这有两个明显的问题:

  • 它不处理它的基本情况(当__VA_ARGS__是单个值时)
  • C不支持递归宏

我已经研究了一些递归的宏观黑客攻击,但我不喜欢使用那种程度的预处理器魔法 - 太多的空间让某些东西变得微不足道.我也考虑使用真实的(可能是可变的)函数,但这也需要

  • 放弃短路行为
  • 将函数作为指针传递,从而规范化它们的签名

并且这两者似乎都比原始的显式代码更糟糕.

我很想听听有关处理此问题的最佳方法的建议.我对许多不同的方法持开放态度,但我的最终目标是在不损害可读性的情况下避免重复.

(我想很明显我在||Ruby这样的语言中对操作符的行为感到嫉妒).

Jon*_*ler 4

我会使用如下代码:

if ((error = do_something()) != 0 ||
    (error = do_something_else(with, some, args)) != 0 ||
    (error = do_something_yet_again()) != 0)
    return error;
return 0;
Run Code Online (Sandbox Code Playgroud)

它是完全定义的,因为每个运算符之前都有序列点||。它实际上不需要宏。仅当您在函数调用之间分配资源或执行其他操作时,它才会遇到问题,但这与示例代码显示的不同。至少 90% 的战斗是创建do_something_or_other()函数序列,以便轻松处理错误排序。