6

很多时候,我必须使用多个以不同方式处理错误或为错误定义自己的枚举的库。这使得编写可能必须处理来自不同来源的错误然后返回其自己的错误代码的函数变得困难。例如:

int do_foo_and_bar()
{
    int err;
    if ((err = libfoo_do_something()) < 0) {
        // return err and indication that it was caused by foo
    }
    if ((err = libbar_do_something()) < 0) {
        // return err and indication that it was caused by bar
    }
    // ...
    return 0;
}

我想到了两种可能的解决方案:

  • 创建我自己的错误代码列表并将这些错误代码转换为新代码,使用类似的函数int translate_foo_error(int err),我将为每个错误编写自己的字符串表示。
  • 创建一个struct my_error同时包含标识库和错误代码的枚举。对字符串的翻译将委托给每个库的适当函数。

这似乎是一个经常出现的问题,所以我很好奇,这通常是如何处理的?似乎前者是大多数图书馆所做的,但后者的工作量较少,并且可以使用已经提供的工具。大多数教程只是向 stderr 打印一条消息并在出现任何错误时退出,这无济于事。我宁愿让每个函数都指出出了什么问题,调用者可以从中决定如何处理它。

4

2 回答 2

2

答案是,这取决于您的代码的约束。

collectd打印到标准错误,然后在遇到致命错误时退出。

OpenGL 将设置一些您可以查询的共享状态。忽略此错误通常会导致未定义的行为。

Collectd 有很多线程问题,大多数错误无法由程序修复或恢复。例如,如果一个插件依赖于某个库,并且对该库的调用失败,则该插件最了解如何从该错误中恢复。冒泡该错误并没有帮助,因为 collectd 核心永远不会知道插件 N+1

另一方面,OpenGL 应用程序通常对遇到的任何错误负责,并且可以尝试纠正错误。例如,如果他们试图编译一个着色器,但可能有一个针对特定供应商或平台的特殊着色器。

根据您的程序设计,请考虑以下事项:

  1. 冒泡错误会让你做出更好的决定吗?
    • 调用者知道你的实现吗?他们应该吗?
  2. 您的应用程序是否可以纠正可能出现的错误?
    • EG 如果您无法打开套接字或文件,您可以重试或失败,仅此而已。
  3. 如果您创建一个GetLastError()函数,是否存在围绕全局状态的限制?

更新:

使用全局状态选项,您可能会遇到这样的情况:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

char* last_err = NULL;

void set_err(char* error_message) {
    if(last_err)
        free(last_err);
    /* Make a deep copy to be safe. 
     * The error string might be dynamically allocated by an external library. 
     * We can't know for sure where it came from. 
     */
    last_err = strdup(error_message); 
}

int can_sqrt(int a) {
    if(a < 0) {
        set_err("We can't take the square root of a negative number");
        return 0;
    }
    return 1;
}

int main(int argc, char* argv[]) {
    int i = 1;
    for(i = 1; i < argc; i++) {
      int square = atoi(argv[i]);
      if(can_sqrt(square)) {
        fprintf(stdout, "the square root of %d is: %.0f\n", square, sqrt(square));
      } else {
        fprintf(stderr, "%s\n", last_err);
      }
    }
    return 0;
}

运行上面的程序

$ ./a.out -1 2 -4 0 -6 4
We can't take the square root of a negative number
the square root of 2 is: 1
We can't take the square root of a negative number
the square root of 0 is: 0
We can't take the square root of a negative number
the square root of 4 is: 2
于 2014-07-09T14:50:03.190 回答
0

我喜欢使用 Thread-Local-Storage (TLS) 来存储在库深处检测到的错误。它们快速且线程安全。唯一真正的问题是错误属于调用生成错误的函数的线程。这可能是某些线程模型中的问题(例如,线程池中的匿名线程)。其他线程看不到错误,除非您有某种方法可以将错误从一个线程传播到另一个线程。但是也有办法做到这一点,而且这种错误传播方法快速高效,并且可以在库中生成更优雅的代码(我相信)。一般的理念是将错误报告以及恢复决策向上推向界面。错误恢复可以在调用堆栈中的任何级别进行处理(例如在接口之前的中间层级别),但总体思路是库中的每个函数都将责任向上推向调用者的方向。每个功能都应该承担一点责任,然后将其余部分传递回调用链。例如,库中的每个(可能是大多数)函数都可以将任何错误记录到 TLS 并返回一个布尔值,指示函数/操作是否成功给调用者。然后,调用者可以查看返回的布尔值,如果操作不成功,要么决定对它做点什么(比如重试),要么中止,清理堆栈并返回 false。如果您存储在 TLS 中的信息是一个结构,您可以在调用链上汇总错误信息(以及采取的任何补救措施)。然后这个过程可以一直持续到接口级别。在任何时候,调用者都可以询问最后一个错误并根据指示的错误决定做任何事情。显然,您的库需要提供顶级的 SetLastError()/GetLastError() 接口函数对。此外,您可能会在库中的每个接口函数(当然除了 SetLastError()/GetLastError() 之外)有代码,该代码会在调用接口函数时重置最后一个错误状态。显然,您的库需要提供顶级的 SetLastError()/GetLastError() 接口函数对。此外,您可能会在库中的每个接口函数(当然除了 SetLastError()/GetLastError() 之外)有代码,该代码会在调用接口函数时重置最后一个错误状态。显然,您的库需要提供顶级的 SetLastError()/GetLastError() 接口函数对。此外,您可能会在库中的每个接口函数(当然除了 SetLastError()/GetLastError() 之外)有代码,该代码会在调用接口函数时重置最后一个错误状态。

于 2015-11-24T07:28:25.270 回答