我有以下catch子句:
catch(Widget w);
catch(Widget& w);
void passAndThrowWidget() {
Widget localWidget;
throw localWidget;
}
如果我们按值捕获Widget对象,编译器将进行复制,因此当我们抛出异常时,localWidget超出范围,我们看不到任何问题。
如果我们通过引用捕获widget对象,根据参考概念,“w”指向相同的本地Widget而不是副本。但我已经看到大多数异常都是由C ++中的引用捕获的。我的问题是如何工作“localWidget”在抛出异常时超出范围,并通过引用捕获指向被破坏的对象。
谢谢!
答案 0 :(得分: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;
}
此外,未指定返回或抛出的内容是return
或throw
语句的表达式或该表达式的副本(或移动)的结果。
通过引用捕获的通常动机与生命无关 - 抛出对象的生命周期保证至少与catch子句一样长。它是首选,因为它允许异常设计和多态使用。
答案 1 :(得分:3)
C ++运行时使用独立于堆栈的内存位置来存储异常对象:
2.4.2分配异常对象
存在异常需要存储 抛出。此存储必须保留 堆栈正在解开,因为它 将由处理程序使用,并且必须 是线程安全的。异常对象 因此通常存储 虽然在堆中分配 实现可以提供 紧急缓冲以支持投掷 低内存下的
bad_alloc
例外 条件(见Section 3.3.1)。
(来自C++ ABI for Itanium: Exception Handling)
因此,当您“通过引用捕获”时获得的引用是对该内存位置的引用,而不是对已释放的堆栈帧的引用。这意味着异常对象可以保证足够长,以供异常处理程序使用,即使通过引用获得也是如此。 (但是,一旦你离开捕获范围,它们可能会被释放,因此不要保留异常引用。)
答案 2 :(得分:2)
例外是本地范围规则的例外:
try
{
Widget w;
throw w;
}
catch (const Widget& exc)
{
// exc is a valid reference to the Widget
}
即使本地范围已经结束,异常也会以特殊方式处理,因此抛出的内容仍然可以访问。
答案 3 :(得分:0)
在这一行中,您要创建本地对象的副本
throw localWidget;
因此,它不引用本地“localWidget”对象,而是引用该对象的副本(称为异常对象),该对象保证在catch子句完全处理异常之前存在
答案 4 :(得分:0)
抛出的实例在抛出时被复制。所以无论如何你总会得到一份副本。
最好通过引用捕获,因为抛出的对象可能是多态的,并且您不希望依赖于由多态类的副本生成的“错误代码”。 '错误代码'不会特定于抛出点抛出的派生类。