throw, try {} catch{}应该如何在现实世界中使用
How throw, try {} catch {} should be used in the real world?
我的意思是,我知道关于throw, try {} catch{}的所有语言规则,但我不确定我是否在现实世界中正确使用它们。请看下面的例子:
我们有一大块科学代码,它做了各种各样的图像处理,最近我们决定把它整理一下,让它更健壮。其中一个常用的例程是void rotate_in_place(float* image, image_size sz);
为了使它更健壮,我们在代码的开头添加了一些完整性检查:
void rotate_in_place(float* image, image_size sz) {
// rotate_in_place does not support non-square image;
if (sz.nx != sz.ny) throw NonSquareImageError;
// rotate_in_place does not support image too small or too large
if (sz.nx <= 2 || sz.nx > 1024) throw WrongImageSizeError;
// Real rode here
.....
}
现在的问题是,rotate_in_place()在超过1000个地方使用,我是否应该用try{} catch{}包装每次调用rotate_in_place(),这看起来会使代码难以置信的膨胀。另一种可能是不包装任何try{} catch{}并让程序退出,但是这与仅仅使用
有什么不同?if (sz.nx != sz.ny) {
cerr << "Error: non-squared image error!n";
exit(0);
}
总之,我不太确定使用throw, try, catch的真正好处,有什么好的建议吗?
每个处理错误的站点都需要try
- catch
块。这完全取决于你的设计,但我怀疑你需要处理每个rotate_in_place
调用站点的错误,你可能会在大多数时候远离向上传播。
打印错误并使用exit
有三个原因:
- 你无法处理错误。
exit
不处理(除非当错误是绝对严重的,但你的函数不能知道调用者可能有恢复的方法)。 - 你通过写入硬编码流来扩展函数的职责,甚至可能不可用(这是
rotate_in_place
,而不是rotate_in_place_and_print_errors_and_kill_the_program_if_something_is_wrong
) -这会损害可重用性。 - 使用这种方法会丢失所有调试信息(您可以从未处理的异常生成堆栈跟踪,您不能对每次都跳出的函数做任何事情-未处理的异常是一个错误,但这是一个您可以跟踪到源代码的错误)。
例外的一般规则是,"立即调用站点关心这里发生了什么吗?"如果调用站点确实关心,那么返回状态码可能是有意义的。否则,throw更有意义。
可以这样考虑——当然,你的原地旋转方法有几个无效的参数类型,在这种情况下,你可能应该抛出std::invalid_argument
。例如,rotate_in_place
的调用者不太可能想要处理或知道如何处理图像不是正方形的情况,因此可能更好地表示为异常。
另一种可能是不包装任何try{} catch{},让程序退出,但这与仅仅使用
有什么不同?if (sz.nx != sz.ny) { cerr << "Error: non-squared image error!n"; exit(0); }
这是不同的,因为如果有人以后想要使用你的函数并将其放入,比如说,GUI应用程序中,他们不必基于错误终止程序。他们可以把那个异常变成对用户来说很漂亮的东西或者类似的东西。
它现在对您也有好处——也就是说,您不必将<iostream>
拉到翻译单元中只是为了进行错误写入。
我通常使用这样的模式:
int realEntryPoint()
{
//Program goes here
}
int main()
{
//Allow the debugger to get the exception if this is a debug binary
#ifdef NDEBUG
try
#endif
{
return realEntryPoint();
}
#ifdef NDEBUG
catch (std::exception& ex)
{
std::cerr << "An exception was thrown: " << ex.what() << std::endl;
}
#endif
}
现在的问题是rotate_in_place()在超过1000个地方使用,我是否应该用
try{} catch {}
包装rotate_in_place()
的每个调用,这在我看来会使代码变得非常臃肿。
会的,而且它违背了使用异常的初衷。
另一种可能是不包装任何try{} catch{}并让程序退出,但是这与仅仅使用[…]
有什么不同呢?
以后总是可以更改异常处理的位置。如果在某个时候您找到了一个更好的地方来合理地处理错误(可能从中恢复),那么您可以将catch
放在这里。有时它就在你抛出异常的函数中;有时它在调用链的顶端。
在main
中加入catch
-all,以防万一。从标准异常(如std::runtime_error
)中派生异常使此操作容易得多。
使用异常处理的意义在于以下几个简单的规则:
- 一旦由于错误的用户输入(内部逻辑应该通过断言/日志处理)而发生任何不好的事情,抛出异常。尽可能快地抛出异常:与。net异常相比,c++异常通常相当便宜。
- 如果你不能处理错误,让异常传播。这意味着几乎总是。
要记住的是:异常应该膨胀到可以处理的程度。这可能意味着一个带有某些格式化错误的对话框,或者这可能意味着一些不重要的逻辑片段最终不会被执行,等等。
使用异常允许调用者决定如何处理错误。如果在函数中直接调用exit
,那么在调用者无法决定如何处理错误的情况下,程序将退出。另外,在exit
中,堆栈对象不会被展开。:-(
你能做的是使rotate_in_place返回一个布尔值,如果函数调用成功。并通过函数参数返回旋转后的图像。
bool rotate_in_place(float* image, image_size sz, float** rotated_image) {
// rotate_in_place does not support non-square image;
if (sz.nx != sz.ny) return false;
// rotate_in_place does not support image too small or too large
if (sz.nx <= 2 || sz.nx > 1024) return false;
// Real rode here
.....
return true;
}
看情况
异常通常要被捕获/处理。在您的情况下,是否有可能处理异常(例如,用户提供了一个非正方形图像,因此您要求他们再试一次)。然而,如果你对此无能为力,那么cerr
就是你要走的路。
好吧,我同意真正使用异常会导致代码臃肿。这就是我不喜欢他们的主要原因。
无论如何,对于你的例子:抛出异常和仅仅使用exit()之间的关键区别在于,由于异常的处理发生(或应该发生)在生成错误/异常的程序片段之外,你不指定函数/类的用户必须如何处理错误。通过使用异常,您可以进行不同的处理,例如终止程序,报告错误甚至从某些错误中恢复。TLDNR:如果使用异常,异常生成部分的代码不需要指定如何处理异常情况。这发生在外部程序中,可以根据代码的使用方式进行更改。
- 激励'inline'说明符的真实世界示例?
- 使用Unreal C++获取VR耳机的世界位置/方向
- 玩家加速穿越世界(C++)
- 在 OpenGL 中将笛卡尔世界坐标转换为球面局部坐标
- 在没有动态内存的世界中,我是否需要虚拟析构函数?
- C++ Python "try: except:"版本
- 通过 Gazebo 世界插件将静态对象附加到机器人链接
- 编译器是否必须始终删除 try-catch 块(如果它被证明是非抛出的)
- 有没有更好的方法来处理异常? try-catch块真的很丑
- 你好世界在 APUE 第 7 章退出,代码为 0
- 迷失在指针的世界里
- 当无法进行RAII时,如何在C++中"try/finally"?
- 我可以使用 try catch 语句来捕获任何错误而不是具体错误吗?
- 在大型应用程序的main上使用try-catch
- 对于像C++这样的现实世界语言,时间复杂性有什么一致的定义吗
- RapidXML 节点在 try catch 块中具有正确的值,但它在块外为 nullptr
- 如何在Openscenegraph中从二维鼠标点击的屏幕坐标点计算三维点(世界坐标)
- 如何暂停进程中的所有线程(停止世界)
- 提取 try-catch 时出现运行时错误
- throw, try {} catch{}应该如何在现实世界中使用