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
为什么在这里重新抛出异常(我假设是这样,否则为什么两个catch
es 会触发?)?这是标准规定的还是编译器错误?我正在使用 GCC 10.2.0(Rev9,由 MSYS2 项目构建)。
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
,而且根本没有调用字符串的构造函数。它甚至不是默认构造的。它的内部状态是完全未定义的。因此,该程序将被简单地破坏。
这是重要的,在这样的异常传播,以避免这样的混乱。
先解决这个问题:请记住,初始化列表之所以重要,是因为成员变量可以直接初始化,而无需事先调用其默认构造函数。