goto或throw,优点/缺点

时间:2015-01-15 08:52:03

标签: c++

我有这段代码:

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

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

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

3 个答案:

答案 0 :(得分:8)

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

我认为,用于替换此以进行错误处理的最惯用模式是,使用std::unique_ptrstd::shared_ptr使您的C库为您提供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 的末尾处理。如果不存在,那将是一个问题。

当然,如果你试图自己使用它们也会有问题,这可能是禁止它的另一部分原因。

除非你说throw;,否则

1 ,在这种情况下,处理处理器中的处理异常。

答案 1 :(得分:2)

关于goto的简单规则(几乎总是如此):假设您不知道使用goto的具体原因,那么请不要这样做。基本上,如果你不能向别人解释“我在这里使用goto,因为...”(并且“因为...”是一个有效且充分的理由,而不仅仅是“我是一个懒惰的程序员,并不能打扰添加另一个级别,如果/ loop / etc“[这是我的借口每一次又一次!])。

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

使用throw还允许你有一个以上的函数调用级别,只有一个throw(换句话说,你可以在一个相当深的调用堆栈中,并且一直到catch进一步的某些级别。)

正如Wintermute所指出的那样,它也不是有效的C ++代码,因此即使你有充分的理由这样做,它也可能无法“按预期工作”。然后你必须在捕获之前做一个goto,然后在那里做一个进入catch块。

答案 2 :(得分:2)

除了 [1] 之外,所有关于你的想法合法性的担忧,忽略了从C ++ 11开始使用std::current_exception的可能性 [2] ,你无法从catch-all子句中以有意义的方式处理异常......

...除非你重新抛出,否则你根本不关心发生了什么样的异常。但是,如果不知道发生了什么异常,除了杀死进程之外你会做什么呢。

首先处理异常的关键在于处理不仅仅是崩溃和刻录(或者你可以让编译器调用terminate,这样更简单,代码更少!) 。但处理需要以某种方式有意义 这就是为什么throw;在这个常用习语中使用的例子远远优于goto

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

// ...
try{ /* ... */ }
catch(...) { handler(); }

是的,goto本质上并不是邪恶的,但只应该在非常罕见的情况下使用它,它实际上使代码更好,更简洁,更易读。这不是这种情况。

你可能会认为重新抛出是昂贵的,但这不是一个有效的论据。异常发生异常,但是一旦遇到异常,性能就不再那么重要了。


[1] 很确定它不符合标准,但我相信GCC会让你这么做,goto上有一些奇怪的扩展让你做有趣的事情。
[2] 虽然将异常传递给另一个线程可能很有用,但与&#34;适当的&#34;相比,它是非常丑陋和笨拙的。溶液