如何处理 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
/catch
或 main()
已终止。
答案取决于s_toState
“承诺”要做的事情。
如果state
无效是函数的程序员错误或其他内部逻辑错误。添加 assert
和文档状态有效性作为前提条件(与指针的 !=nullptr
相同)。对于发布版本,添加一些默认行为,以便程序在可能的情况下不会崩溃。或者如果你不写关键软件就让它崩溃,它至少会让调试更容易。
如果是用户的可恢复故障和合理的场景(不是错误),则考虑返回 std::optional
或 throw
。当抛出的异常总是被直接调用者捕获时,我更喜欢前者,即永远不会向上传播调用堆栈。我也发现它更清楚,因为它强制调用者显式处理它。
对于不可恢复的故障,即当直接调用者无法处理 nullopt
时,只需抛出并让其他代码处理。
我喜欢 try_parse
返回 std::optional
而 parse
抛出的命名约定。
可能值得 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()
但你可能更喜欢其他方式。
所以,我对 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
/catch
或main()
已终止。
答案取决于s_toState
“承诺”要做的事情。
如果state
无效是函数的程序员错误或其他内部逻辑错误。添加 assert
和文档状态有效性作为前提条件(与指针的 !=nullptr
相同)。对于发布版本,添加一些默认行为,以便程序在可能的情况下不会崩溃。或者如果你不写关键软件就让它崩溃,它至少会让调试更容易。
如果是用户的可恢复故障和合理的场景(不是错误),则考虑返回 std::optional
或 throw
。当抛出的异常总是被直接调用者捕获时,我更喜欢前者,即永远不会向上传播调用堆栈。我也发现它更清楚,因为它强制调用者显式处理它。
对于不可恢复的故障,即当直接调用者无法处理 nullopt
时,只需抛出并让其他代码处理。
我喜欢 try_parse
返回 std::optional
而 parse
抛出的命名约定。
可能值得 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()
但你可能更喜欢其他方式。