为什么在完全包含在 try-catch 中的构造函数中抛出的异常似乎被重新抛出?

Fur*_*ish 11 c++ constructor try-catch

考虑到这个看起来很傻的try-catch链条:

try {
    try {
        try {
            try {
                throw "Huh";
            } catch(...) {
                std::cout << "what1\n";
            }
        } catch(...) {
            std::cout << "what2\n";
        }
    } catch(...) {
        std::cout << "what3\n";
    }
} catch(...) {
    std::cout << "what4\n";
}
Run Code Online (Sandbox Code Playgroud)

它的输出肯定是(并且是)what1,因为它将被最接近的匹配捕获catch。到现在为止还挺好。

但是,当我尝试为尝试通过成员初始化列表(这将导致引发异常)初始化成员的类创建构造函数时,如下所示:

int might_throw(int arg) {
    if (arg < 0) throw std::logic_error("que");
    return arg;
}

struct foo {
    int member_;

    explicit foo(int arg) try : member_(might_throw(arg)) {

    } catch (const std::exception& ex) { std::cout << "caught1\n"; }
};

int main() {
    try {
        auto f = foo(-5);
    } catch (...) { std::cout << "caught2\n"; }
}
Run Code Online (Sandbox Code Playgroud)

程序的输出现在是:

抓到1

抓到2

为什么在这里重新抛出异常(我假设是这样,否则为什么两个catches 会触发?)?这是标准规定的还是编译器错误?我正在使用 GCC 10.2.0(Rev9,由 MSYS2 项目构建)。

Pau*_*ers 20

cppreference 有这个关于函数尝试块的说法(这就是我们在这里拥有的):

构造函数的函数 try 块中的每个 catch 子句都必须通过抛出异常终止。如果控件到达此类处理程序的末尾,则当前异常会像 throw 一样自动重新抛出。

因此,我们有它。当catch构造函数的成员初始化列表退出时,您的异常会自动重新抛出。我猜逻辑是你的构造函数被认为已经失败(在构造函数中的异常处理程序执行任何清理之后,也许)异常会自动传播给调用者。


Fra*_*ank 10

虽然另一个答案给出了一个很好的官方解释,但也有一种非常直观的方法来了解为什么事情必须以这种方式行事:考虑替代方案。

int用 a替换了string使问题显而易见,但同样的原则也适用于算术类型。

std::string might_throw(const std::string& arg) {
    if (arg.length() < 10) throw std::logic_error("que");
    return arg;
}

struct foo {
    std::string member_;

    explicit foo(const std::string& arg) try : member_(might_throw(arg)) {

    } catch (const std::exception& ex) { std::cout << "caught1\n"; }
};

int main() {
    try {
        auto f = foo("HI");

        std::cout << f.member_ << "\n"; // <--- HERE

    } catch (...) { std::cout << "caught2\n"; }
}
Run Code Online (Sandbox Code Playgroud)

如果异常没有传播,会发生什么?

不仅没有arg达到member,而且根本没有调用字符串的构造函数。它甚至不是默认构造的。它的内部状态是完全未定义的。因此,该程序将被简单地破坏。

这是重要的,在这样的异常传播,以避免这样的混乱。

先解决这个问题:请记住,初始化列表之所以重要,是因为成员变量可以直接初始化而无需事先调用其默认构造函数。