为什么在完全包含在try-catch中的构造函数中抛出的异常似乎被重新抛出?
考虑到这个看起来很傻的try-catch链条:
try {
try {
try {
try {
throw "Huh";
} catch(...) {
std::cout << "what1n";
}
} catch(...) {
std::cout << "what2n";
}
} catch(...) {
std::cout << "what3n";
}
} catch(...) {
std::cout << "what4n";
}
它的输出肯定是(并且是)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 << "caught1n"; }
};
int main() {
try {
auto f = foo(-5);
} catch (...) { std::cout << "caught2n"; }
}
程序的输出现在是:
抓到1
抓到2
为什么在这里重新抛出异常(我假设是这样,否则为什么两个catches 会触发?)?这是标准规定的还是编译器错误?我正在使用 GCC 10.2.0(Rev9,由 MSYS2 项目构建)。
回答
cppreference 有这个关于函数尝试块的说法(这就是我们在这里拥有的):
构造函数的函数 try 块中的每个 catch 子句都必须通过抛出异常终止。如果控件到达此类处理程序的末尾,则当前异常会像 throw 一样自动重新抛出。
因此,我们有它。当catch构造函数的成员初始化列表退出时,您的异常会自动重新抛出。我猜逻辑是你的构造函数被认为已经失败(在构造函数中的异常处理程序执行任何清理之后,也许)异常会自动传播给调用者。
回答
虽然另一个答案给出了一个很好的官方解释,但也有一种非常直观的方法来了解为什么事情必须以这种方式行事:考虑替代方案。
我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 << "caught1n"; }
};
int main() {
try {
auto f = foo("HI");
std::cout << f.member_ << "n"; // <--- HERE
} catch (...) { std::cout << "caught2n"; }
}
如果异常没有传播,会发生什么?
不仅没有arg达到member,而且根本没有调用字符串的构造函数。它甚至不是默认构造的。它的内部状态是完全未定义的。因此,该程序将被简单地破坏。
这是重要的,在这样的异常传播,以避免这样的混乱。
先解决这个问题:请记住,初始化列表之所以重要,是因为成员变量可以直接初始化,而无需事先调用其默认构造函数。
THE END
二维码