为什么派生类异常可以被基类捕获子句捕获

Why derived class exception could be caught by base class catch clause.

本文关键字:基类 子句 派生 异常 为什么      更新时间:2023-10-16

我尝试过在Xcode上遵循代码

#include <iostream>

/*Exceptions*/
struct A {
    A( int value ) : m_value( value ) {}
    int m_value;
};
struct B : A {
    B( int value ) : A( value ) {}
};
//+++++++++++++++++++++++++++++++
/*Exceptions End*/
int main(int argc, const char * argv[]) {
    try {
        try {
            throw B( 5 );
        }
        catch ( A a ) {
            a.m_value *= 2;
        }
        catch ( B b ) {
            b.m_value -= 2;
            throw b;
        }
    }
    catch ( A a ) {
        std::cout << a.m_value;
    }
    return 0;
}

此处抛出的异常类型是 B,但catch( A a )捕获了 B。

我对此有一些想法,但不知道它是否正确。我认为这是因为 A 的复制构造函数接受可以匹配 B 类型对象的const A&,并且复制构造函数隐式地将数据类型从 B 转换为 A。为了确认这一点,我为 struct A 添加了复制构造函数:

A( const A& other ) : m_value( other.m_value ) {
        std::cout << "hellon";
}

这在执行catch( A a )时确实输出 hello ,但是当我显式定义复制构造函数时,如下所示:

explicit A( const A& other ) : m_value( other.m_value ) {
        std::cout << "hellon";
}

编译器大喊"没有匹配的构造函数来初始化'A'"。

我不知道为什么。为什么不直接跳到catch( B b )

[except.handle]/4 中涵盖了这个确切的问题:

try 块的处理程序按出现顺序进行尝试。这使得编写处理程序成为可能 永远不能执行,例如,通过将派生类的处理程序放在相应 基类。

如果您在启用警告的情况下进行编译,这也将变得清晰:

main.cpp:28:9: warning: exception of type 'B' will be caught
         catch ( B b ) {
         ^
main.cpp:24:9: warning:    by earlier handler for 'A'
         catch ( A a ) {
         ^

所以是的,最终发生的事情是一旦抛出B,我们只是逐个查看处理程序列表。我们可以A赶吗?是的,我们能!B可转换为A

现在,当你制作A的复制构造函数时,发生了一些有趣的事情。你不能隐式地将B转换为A,但这不是异常逻辑处理的作用。它只是检查类型。根据 [except.handle]/3:

处理程序是类型 E 的异常对象的匹配项,如果
— [...]
— 处理程序的类型为 cv Tcv T& T E 的明确公共基类,或
— [...]

在我们的例子中,A 是一个明确的 B 公共基类,处理程序的类型是 A ,因此处理程序匹配。句点。现在,事实证明我们实际上无法使用处理程序,因此代码格式不正确。

因为catch (A ...)是第一位的。这是C++的一个错误特征。理想情况下,它会给你一个编译错误,因为catch块的顺序不对,或者至少是无法访问的代码。把catch (B ...)放在第一位。