【问题标题】:Best practice for error reporting with std::source_location使用 std::source_location 报告错误的最佳实践
【发布时间】:2020-11-02 09:22:10
【问题描述】:

在 C++20 中引入的新标准库工具中,有 std::source_location——一个允许我们方便地捕获和处理以前只能通过实现定义的宏(如 __FILE__ 或 __LINE__)访问的信息的类.我的理解是它的预期用途是记录和错误报告功能。例如,一个函数声明为

void log(const char* message, std::source_location src_loc = std::source_location::current())

可以记录一条消息,包括源文件名和来自其调用位置的行。然而,经过一些实验,我很想更广泛地使用source_location,我想知道这是否是一个好习惯。

举个例子,假设我正在开发一个应用程序,它可能会遇到一个特定的错误,它没有合理的恢复方式。唯一要做的就是关闭,但我想确保记录一些有关导致错误的情况的有用信息。我将介绍一些非常简化的sn-ps代码,在这个问题中我只想专注于报告错误的位置,因此我将跳过有关记录其他信息或关闭程序的详细信息。

假设错误总是发生在函数foo中。一种简单的报告方式是:

int foo(double x)
{
  // ...
  if (error_occurred)
  {
    log(error_message);
    // shutdown
  }
  // ...
}

但是,这将报告来自log 调用站点的错误,这始终与foo 的实现完全相同。这不是很有用,特别是如果foo 是一个从代码中的不同位置多次调用的低级函数。将导致错误的调用位置报告给foo 会更有帮助。我想比较解决这个问题的两种方法。

1。抛出异常

假设我们有像fun_1、fun_2、...这样调用foo的函数。我们可以将foo修改为

int foo(double x)
{
  // ...
  if (error_occurred)
  {
    throw std::runtime_error{error_message};
  }
  // ...
}

然后在fun_1:

try
{
  n = foo(x);
}
catch (const std::runtime_error& error)
{
  log(error.what());
  // shutdown
}

这样log 将报告与调用​​foo 相关联的catch 块的位置。

2。传播source_location

或者,我们可以修改foo的签名和正文:

int foo(double x, std::source_location src_loc = std::source_location::current())
{
  // ...
  if (error_occurred)
  {
    log(error_message, src_loc);
    // shutdown
  }
  // ...
}

然后,在fun_1 中除了n = foo(x); 之外不做任何事情,我们得到与方法1 相同的报告。(甚至稍微好一点,因为它直接指向foo 的调用而不是catch 块。 )

这两种方法都不完全令人满意,因为它们都给代码添加了一些噪音,但我认为这是错误处理不可避免的,问题是如何最小化它。我看到以下问题。

  1. 抛出异常
  • foo 的呼叫站点被严重污染。如果有很多函数,如fun_1 和fun_2,可能每个函数都调用foo 多次,并且我们想准确报告哪个调用导致错误,那么每个函数都必须用try/catch 块装饰.
  1. 传播source_location
  • foo 的签名被污染了。
  • 如果foo 和fun_1 之间的调用堆栈较长,那么我们将不得不污染所有中间函数的签名和调用站点。

我相信方法 1 在 C++ 中是相当标准的做法,但我找不到任何关于按照我建议的方式使用 source_location 的信息。这会是个好主意吗?使用source_location 作为属于“生产代码”的函数中的参数而不是专用的错误处理函数是否合理?这会导致我忽略的任何问题吗?

【问题讨论】:

    标签: c++ error-handling


    【解决方案1】:

    考虑选项 3

    int foo(double x)
    {
      // ...
      if (error1_occurred)
      {
        log(error_message, std::source_location::current());
        // shutdown
      } else if (error2_occured) {
        log(error_message, std::source_location::current());
      }
      // ...
    }
    

    当出现多个错误时,您想知道它是从哪里来的。您想知道错误发生的源位置,而不是调用堆栈的某个位置。这不是噪音,但它为您的日志输出添加了有价值的信息。

    另一方面,如果您确实想在某个地方收集信息,请考虑选项 4:

    int foo(double x)
    {
      // ...
      if (error_occurred)
      {
        throw my_error{error_message,std::source_location::current()};
      }
      // ...
    }
    

    这不是很有用,特别是如果 foo 是一个低级函数,从代码中的不同位置多次调用

    这与旧的 __FILE__ 和 __LINE__ 宏没有什么不同。它们显示了它们被预处理器替换的文件/行。 std::source_location 工作方式类似。

    话虽如此,您可以决定要在日志中包含哪些信息。没有万能的解决方案。

    【讨论】:

    • 你根本没有解决我的问题。您的选项 3 是我的“选项 0”。问题的整个前提是这可能是不够的,因为foo 可能没有足够的信息来说明错误发生的原因。就它而言,没有error1_occurred或error2_occurred,只有error_occurred。您的选项 4 没有完成选项 1 之外的任何事情。它抛出哪种类型的异常并不重要,因为它没有异常类型可以传达的信息。
    猜你喜欢
    • 2011-12-15
    • 1970-01-01
    • 2013-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-03
    • 2020-10-18
    相关资源
    最近更新 更多