使用std::source_location报告错误的最佳实践

Best practice for error reporting with std::source_location

本文关键字:最佳 错误 location std source 使用 报告      更新时间:2023-10-16

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

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

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

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

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

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

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

1.抛出异常

假设我们有类似fun_1fun_2、…的函数,。。。其呼叫CCD_ 12。我们可以将foo修改为

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

然后在CCD_ 14内部:

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

这样,CCD_ 15将向CCD_ 16报告与呼叫相关联的捕获块的位置。

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_1fun_2这样的函数,可能每个函数都多次调用foo,并且我们想准确地报告是哪个调用导致了错误,那么每个函数都必须由try/catch块修饰
  1. 传播source_location
  • foo的特征被污染
  • 如果foofun_1之间有更长的调用堆栈,那么我们将不得不污染所有中间函数的签名和调用位置

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

考虑选项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());
}
// ...
}

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

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

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

这不是很有用,尤其是如果foo是一个低级别函数,它在代码中的不同位置被多次调用

这与旧的__FILE____LINE__宏没有什么不同。它们显示预处理器替换它们的文件/行。std::source_location的工作原理类似。

话虽如此,但由您决定要在日志中包含哪些信息。没有一个一刀切的解决方案。