如何处理 C++ 中的运行时错误?

How to handle runtime errors in C++?

所以,我对 C++ 有点陌生,我想知道什么是好的做法,甚至想知道在编程时如何处理运行时错误,这里有一个例子:

State s_toState(std::string state){
  if (state == "MG")
    return State::MG;
  else if (state == "PR")
    return State::PR;
  else if (state == "SP")
    return State::SP;
  else if (state == "SC")
    return State::SC;
  else if (state == "RJ")
    return State::RJ;
  else if (state == "RN")
    return State::RN;
  else if (state == "RS")
    return State::RS;

  // ???
}

所以我有这个函数可以将 string 转换为 State。在不使用异常的情况下,我断言给定状态是现有状态(MG、PR、SP 等...)的理想方式是什么?

举了一个例子,但我要求的是一般规则。据我所知,我可以使用异常、断言或只打印错误。我也假装对此进行单元测试(也是单元测试的新手,对此一无所知)。

这看起来是一个使用异常的好机会。 cplusplus.com guide 是合理的,但我发现玩弄它是更好的学习方法。

基本思路是这样的:

  • 一个函数 throw 是一个异常,终止该函数并将异常传递给调用该函数的人。
  • 如果调用者在try块中调用该函数,则将执行后续的catch
  • 如果调用者没有 try/catch 系统,调用者也会终止,进程会重复函数调用堆栈,直到找到 try /catchmain() 已终止。

答案取决于s_toState“承诺”要做的事情。

如果state无效是函数的程序员错误或其他内部逻辑错误。添加 assert 和文档状态有效性作为前提条件(与指针的 !=nullptr 相同)。对于发布版本,添加一些默认行为,以便程序在可能的情况下不会崩溃。或者如果你不写关键软件就让它崩溃,它至少会让调试更容易。

如果是用户的可恢复故障和合理的场景(不是错误),则考虑返回 std::optionalthrow。当抛出的异常总是被直接调用者捕获时,我更喜欢前者,即永远不会向上传播调用堆栈。我也发现它更清楚,因为它强制调用者显式处理它。

对于不可恢复的故障,即当直接调用者无法处理 nullopt 时,只需抛出并让其他代码处理。

我喜欢 try_parse 返回 std::optionalparse 抛出的命名约定。

可能值得 Exceptions and Error Handling FAQ 试一试。

一般来说,处理此类错误(与任何错误一样)的方式取决于整个程序的需要——而您并未具体说明。所以没有放之四海而皆准的“通则”。

有多种选择和取舍。

一个选项是让您的 State 枚举类型提供一个代表未确定或无效状态的枚举器值,例如

 enum class State {MG, PR, Undetermined};

然后,在您的函数中,return 未确定的值,例如

State s_toState(const std::string &state)
{
    State return_value = State::Undetermined;
    if (state == "MG")
       return_value = State::MG;
    else if (state == "PR")
       return_value = State::PR;
    // etc

    return return_value;
}

使用这种方法,函数始终 return 是类型 State 的有效值。如果错误条件不严重(即,如果提供了无效字符串,程序可以继续),则调用者可以决定是否需要检查 return 值。可以报​​告多种类型的错误情况(例如,通过多个枚举值表示不同的错误)不利的一面是调用者可能会忘记检查 return 值并且行为不正确。

另一种选择是抛出异常,例如;

State s_toState(const std::string &state)
{
    if (state == "MG")
       return State::MG;
    else if (state == "PR")
       return State::PR;
    // etc

    throw std::invalid_argument("Bad input string");
}

此选项需要对抛出的异常类型进行一些明智的选择(例如,需要传达有关错误状态的哪些信息)。如果调用者(或整个程序)在提供错误字符串时无法明智地继续,则此方法可能更可取,因为通过抛出异常,调用者被迫捕获并采取任何恢复操作以避免被终止。因此,这种方法可能不适用于非关键错误 - 例如,如果提供了错误的字符串,执行可以明智地继续。

另一个选项(C++17 及更高版本)是 return std::optional<State>。这允许调用者检查是否发生了错误(例如 std::option::has_value() return false),或者,如果在没有检查的情况下访问了值,则导致 std::bad_optional_access 类型的异常被抛出(这可能适合调用者,或者可能没有信息)。

也可以使用 assert() - 如果指定条件不正确,它会强制终止。一般来说,我更喜欢抛出异常而不是使用 assert() 但你可能更喜欢其他方式。