问题 C ++异常catch子句[重复]


可能重复:
C ++中异常对象的范围 

我有以下catch子句:

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

void passAndThrowWidget() {
          Widget localWidget;
      throw localWidget;
}

如果我们按值捕获Widget对象,编译器将进行复制,因此当我们抛出异常时,localWidget超出范围,我们没有看到任何问题。

如果我们通过引用捕获小部件对象,根据参考概念,“w”指向相同的本地Widget而不是副本。但我已经看到大多数异常都是由C ++中的引用捕获的。我的问题是如何工作“localWidget”超出范围时抛出异常并通过referense指向被破坏的对象。

谢谢!


11003
2017-07-06 04:55


起源



答案:


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子句一样长。它是优选的,因为它允许以多态方式设计和使用异常。


9
2017-07-06 05:05





C ++运行时使用独立于堆栈的内存位置来存储异常对象:

2.4.2分配异常对象

例外情况需要存储   抛出。此存储必须保留   堆栈正在解开,因为它   将由处理程序使用,并且必须   是线程安全的。异常对象   因此通常存储   虽然在堆中分配   实现可以提供   紧急缓冲,以支持投掷    bad_alloc 低内存下的异常   条件(见 第3.3.1节)。

(来自 用于Itanium的C ++ ABI:异常处理

因此,当您“通过引用捕获”时获得的引用是对该内存位置的引用,而不是对已释放的堆栈帧的引用。这意味着异常对象可以保证足够长,以供异常处理程序使用,即使通过引用获取也是如此。 (但是,一旦你离开捕获范围,它们可能会被释放,因此不要保留异常引用。)


3
2017-07-06 05:05





例外是本地范围规则的例外:

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

即使本地范围已经结束,异常也会以特殊方式处理,因此抛出的内容仍然可以访问。


2
2017-07-06 05:04



+1,如果这是真的那么一个合理的答案。 - iammilind
是 return w; 那么一个例外吗? - Luc Danton
@Luc Danton,不是C ++。异常有一个复杂的API来支持它们并强制堆栈展开。返回语句只是通过将数据放入寄存器或堆栈上的调用者已知的位置,将数据移交给前一帧;异常以这样一种方式传播,即呼叫者甚至可能完全不知道它们。还有其他语言,其中返回值是异常(或异常是返回值,无论哪个浮动您的船)。 - zneak
@Luc,哦,你的评论突然变得更有意义了。我去睡了。 - zneak
@Luc:不,如果你通过引用返回本地,你会得到一个悬空引用。返回值和抛出的异常在生命周期方面非常不同。 - Ben Voigt


答案:


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子句一样长。它是优选的,因为它允许以多态方式设计和使用异常。


9
2017-07-06 05:05





C ++运行时使用独立于堆栈的内存位置来存储异常对象:

2.4.2分配异常对象

例外情况需要存储   抛出。此存储必须保留   堆栈正在解开,因为它   将由处理程序使用,并且必须   是线程安全的。异常对象   因此通常存储   虽然在堆中分配   实现可以提供   紧急缓冲,以支持投掷    bad_alloc 低内存下的异常   条件(见 第3.3.1节)。

(来自 用于Itanium的C ++ ABI:异常处理

因此,当您“通过引用捕获”时获得的引用是对该内存位置的引用,而不是对已释放的堆栈帧的引用。这意味着异常对象可以保证足够长,以供异常处理程序使用,即使通过引用获取也是如此。 (但是,一旦你离开捕获范围,它们可能会被释放,因此不要保留异常引用。)


3
2017-07-06 05:05





例外是本地范围规则的例外:

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

即使本地范围已经结束,异常也会以特殊方式处理,因此抛出的内容仍然可以访问。


2
2017-07-06 05:04



+1,如果这是真的那么一个合理的答案。 - iammilind
是 return w; 那么一个例外吗? - Luc Danton
@Luc Danton,不是C ++。异常有一个复杂的API来支持它们并强制堆栈展开。返回语句只是通过将数据放入寄存器或堆栈上的调用者已知的位置,将数据移交给前一帧;异常以这样一种方式传播,即呼叫者甚至可能完全不知道它们。还有其他语言,其中返回值是异常(或异常是返回值,无论哪个浮动您的船)。 - zneak
@Luc,哦,你的评论突然变得更有意义了。我去睡了。 - zneak
@Luc:不,如果你通过引用返回本地,你会得到一个悬空引用。返回值和抛出的异常在生命周期方面非常不同。 - Ben Voigt


在这一行中,您将创建本地对象的副本,

throw localWidget;

因此,它不会引用您的本地“localWidget”对象,而是引用该对象的副本(称为 异常对象保证在catch子句完全处理异常之前一直存在。


0
2017-07-06 05:10





抛出的实例在抛出时被复制。所以无论如何你总会得到一份副本。

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


0
2017-07-06 05:55