C++异常捕获语句

11

可能有重复:
C++中异常对象的作用域

我有以下的异常捕获语句:

catch(Widget w);
catch(Widget& w);

void passAndThrowWidget() {
          Widget localWidget;
      throw localWidget;
}
如果我们按值捕获Widget对象,编译器会进行复制,因此当我们抛出异常时,localWidget超出范围,我们不会遇到任何问题。 如果我们通过引用捕获widget对象,则根据引用概念,“w”指向同一本地Widget而不是副本。但我看到大多数异常在C ++中通过引用捕获。我的问题是,当抛出异常并通过引用捕获指向被销毁的对象时,“localWidget”如何工作? 谢谢!
5个回答

9

throw expr;return expr;类似,都使用复制初始化(在C++0x中也可以使用列表初始化)。但这主要是语法问题。

至于语义,从非引用类型的函数返回值是可以的,同样抛出异常也是可以的:

T f()
{
    // t is local but this is clearly fine
    T t;
    return t;

    // and so is this
    throw t;
}

此外,未指定返回或抛出的是returnthrow语句的表达式结果,还是该表达式的副本(或移动)。

通过引用捕获异常通常不涉及生命周期——抛出对象的生命周期保证至少与catch子句一样长。这种方式更可取的原因是允许使用多态设计和使用异常。


3
C++运行时使用一个与堆栈无关的内存位置来存储异常对象:
2.4.2 分配异常对象
抛出异常需要存储空间。这个存储空间必须在堆栈展开时持续存在,因为它将被处理程序使用,并且必须是线程安全的。因此,异常对象存储通常会在堆中分配,尽管实现可以提供一个紧急缓冲区来支持在低内存条件下抛出 bad_alloc 异常(请参见第3.3.1节)。
(来自Itanium C++ ABI:异常处理
当您使用“按引用捕获”时,您获取的引用是指向该内存位置的引用,而不是指向已释放的堆栈帧的引用。这意味着异常对象保证足够长时间存在,以便被您的异常处理程序使用,即使是通过引用方式获取的。但请注意,它们可能会在离开catch作用域后被释放,因此不要保留异常引用。

2

异常是局部作用域规则的例外:

try
{
    Widget w;
    throw w;
}
catch (const Widget& exc)
{
    // exc is a valid reference to the Widget
}

即使局部范围已经结束,异常处理仍然是以特殊方式进行的,因此抛出的异常仍然可以访问。

如果是真的,那么这是一个合理的答案。+1 - iammilind
那么 return w; 也算是一个异常吗? - Luc Danton
@Luc Danton,不是在C++中。异常具有复杂的API来支持它们并强制执行堆栈展开。返回语句仅通过将数据放入寄存器或在位置上将其放在调用者已知的堆栈上向前一帧交回数据;异常以这样的方式传播,以至于调用者甚至可能完全不知道它们的存在。还有其他语言中返回值是异常(或异常是返回值),取决于你喜欢哪种方式。 - zneak
@Luc,哦,你的评论突然更有意义了。我要去睡觉了。 - zneak
2
@Luc:不,如果你通过引用返回一个局部变量,你会得到一个悬空引用。从生命周期的角度来看,返回值和抛出的异常非常不同。 - Ben Voigt
抱歉,Tim,这是错误的。引用“exc”实际上将是使用复制构造函数创建的一个新对象的引用。这是我用来找出的代码,你可以尝试自己运行:http://pastebin.com/CAdnZEyA - David Grayson

0
在这行中,您正在创建本地对象的副本,
throw localWidget;

所以,它并不是指您的本地“localWidget”对象,而是该对象的一个副本(称为异常对象),该副本保证在catch子句完全处理异常之前一直存在。


0

抛出的实例在抛出时会被复制。因此,您总是会得到一个副本。

最好通过引用捕获,因为被抛出的对象可能是多态的,您不希望依赖于由多态类的副本产生的“错误代码”。该“错误代码”将不特定于在throw点处抛出的派生类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接