转到或扔,优点/缺点

goto or throw, advantages/disadvantages

本文关键字:优点 缺点      更新时间:2023-10-16

我有这段代码:

try
{
  // ...do something... possibly goto error
}
catch (...)
{
}
error:
// ...process error...

面临的问题是我是否应该使用goto(如果可能)或throw跳转到error标签。这两种方法的(缺点)优点是什么?

编辑:修复代码以符合标准。

编辑:现在这是一个完全不同的问题,答案也完全不同。

我相信,将其替换为错误处理的最惯用模式是使用自定义删除器将 C 库提供给您的内容std::unique_ptrstd::shared_ptr地提供给 RAII,在这种情况下,正常情况下的特殊处理将不再需要它们。然后,您可以按常规方式处理错误:

// just for a simple example.
std::unique_ptr<void, void(*)(void*)> c_obj(malloc(1000), free);
try {
  // Don't use goto, throw.
} catch(...) {
  // handle error here
}
// label not required anymore, cleanup handled by custom deleters. That means
// that free will be called when c_obj is destroyed.

我强调这是为了错误处理,因为将throw用于正常的控制流是不好的,原因与goto不好的原因相同:代码的结构越像一团意大利面条,就越难以理解和维护。如果这是正常的控制流,我会告诉你寻找以更有条理的方式重述问题的方法。

对原始问题的回答

在这种情况下使用goto格式不正确,如[除了](C++11中的15(3)C++03中的15(2)

):

不得使用 gotoswitch 语句将控制权转移到 try 块或处理程序中。[示例:

void f() {
  goto l1; // Ill-formed
  goto l2; // Ill-formed
  try {
    goto l1; // OK
    goto l2; // Ill-formed
    l1: ;
  } catch(...) {
    l2: ;
    goto l1; // Ill-formed
    goto l2; // OK
  }
}

-- 结束示例] (...

因此,您的编译器完全拒绝此类代码是合理的,实际上 gcc 和 clang 都拒绝编译它。

我怀疑它被禁止的原因与禁止跳过带有初始化的声明的原因相同,即在没有异常的情况下在异常处理程序中并不比在没有变量的变量范围内更有意义。就像具有自动存储持续时间的变量在作用域结束时处理(调用析构函数)一样,异常也可以在异常处理程序1 结束时处理。如果两者都不存在,那将是一个问题。

当然,如果您尝试自己使用它们,那也将是一个问题,这可能是禁止使用它们的另一部分原因。

1将是,除非您说 throw; ,在这种情况下,异常将在处理程序处理。

撇开所有关于你的想法合法性的担忧[1],忽略从C++11开始使用std::current_exception的可能性[2],你没有办法从包罗万象的条款中以有意义的方式处理异常......

。除非你重新抛出或者你根本不关心发生了什么样的异常。但是,在不知道发生了什么异常的情况下,除了杀死进程之外,您还会做什么。

首先处理异常的要点是,处理不仅仅是崩溃和刻录(或者你可以让编译器调用terminate,这更容易,代码更少!但处理需要以某种方式有意义
这就是为什么throw;例如在这个常见的成语中使用的goto远远优于:

void handler()
{
    try { throw; } catch (foo& f){} catch(bar& b){}  /* ... */
}
// ...
try{ /* ... */ }
catch(...) { handler(); }

是的,goto本质上并不是邪恶的,但人们应该只在极少数情况下使用它,它实际上使代码更好、更简洁、更易读。这不是这样一种情况。

你可能会争辩说重新抛出是昂贵的,但这不是一个有效的论点。异常发生异常,但一旦遇到异常,性能就不再重要了。


[1]很确定它不符合标准,虽然我相信GCC无论如何都会让你这样做,但goto上有一些奇怪的扩展可以让你做有趣的事情。
[2]虽然它对于将异常传递给另一个线程可能很有用,但与"适当的"解决方案相比,它非常丑陋和笨拙。

关于goto的简单规则(几乎总是):假设您不知道使用 goto 的具体原因,那么不要。基本上,如果您无法向其他人解释"我在这里使用goto是因为......"(以及"因为..."是一个有效且很好的理由,而不仅仅是"我是一个懒惰的程序员,如果 if/loop/etc 懒得添加另一个级别"[这是我时不时地的借口!

throw将有助于清理,而goto不会,因此在中间范围内需要清理的任何局部变量都不会

使用 throw 还允许您使用一个throw摆脱多个级别的函数调用(换句话说,您可以在一个相当深的调用堆栈中,并一直到更远的某个级别catch)。

正如 Wintermute 指出的那样,它也不是有效的C++代码,因此即使您有很好的理由这样做,它也可能"按预期工作"。然后,您必须在捕获之前执行一个转到,然后在那里进行投掷以进入捕获块。