为什么完全包含在 try-catch 中的构造函数中抛出的异常似乎被重新抛出?
Why does an exception thrown in a constructor fully enclosed in try-catch seem to be rethrown?
考虑到这个看起来很傻的 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";
}
它的输出肯定是(并且是)what1
,因为它会被最接近的匹配 catch
捕获。到目前为止一切顺利。
但是,当我尝试为 class 创建构造函数时 尝试 通过成员初始化列表初始化成员(这将导致引发异常) 像这样:
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"; }
}
程序的输出现在是:
caught1
caught2
为什么这里会重新抛出异常(我假设是这样,否则为什么会触发两个 catch
es?)?这是标准强制要求的还是编译器错误?我正在使用 GCC 10.2.0(Rev9,由 MSYS2 项目构建)。
cppreference 有 this 说明函数尝试块(这是我们这里有的):
Every catch-clause in the function-try-block for a constructor must terminate by throwing an exception. If the control reaches the end of such handler, the current exception is automatically rethrown as if by throw.
好了。当构造函数的成员初始化列表中的 catch
退出时,您的异常会自动重新抛出。我猜逻辑是你的构造函数被认为失败了(在构造函数中的异常处理程序执行任何清理之后,也许)异常会自动传播给调用者。
虽然另一个答案给出了很好的官方解释,但也有一种非常直观的方法可以了解为什么事情必须这样:考虑替代方案。
我已将 int
替换为 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"; }
}
如果异常没有传播应该发生什么?
不仅 arg
从未达到 member
,而且根本没有调用字符串的构造函数。它甚至不是默认构造的。它的内部状态是完全未定义的。所以程序会被简单地破坏。
重要的是异常的传播方式可以避免像这样的混乱。
先发制人:请记住,初始化列表之所以重要,首先是为了让成员变量可以直接初始化,而无需调用其默认构造函数事先.
考虑到这个看起来很傻的 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";
}
它的输出肯定是(并且是)what1
,因为它会被最接近的匹配 catch
捕获。到目前为止一切顺利。
但是,当我尝试为 class 创建构造函数时 尝试 通过成员初始化列表初始化成员(这将导致引发异常) 像这样:
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"; }
}
程序的输出现在是:
caught1
caught2
为什么这里会重新抛出异常(我假设是这样,否则为什么会触发两个 catch
es?)?这是标准强制要求的还是编译器错误?我正在使用 GCC 10.2.0(Rev9,由 MSYS2 项目构建)。
cppreference 有 this 说明函数尝试块(这是我们这里有的):
Every catch-clause in the function-try-block for a constructor must terminate by throwing an exception. If the control reaches the end of such handler, the current exception is automatically rethrown as if by throw.
好了。当构造函数的成员初始化列表中的 catch
退出时,您的异常会自动重新抛出。我猜逻辑是你的构造函数被认为失败了(在构造函数中的异常处理程序执行任何清理之后,也许)异常会自动传播给调用者。
虽然另一个答案给出了很好的官方解释,但也有一种非常直观的方法可以了解为什么事情必须这样:考虑替代方案。
我已将 int
替换为 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"; }
}
如果异常没有传播应该发生什么?
不仅 arg
从未达到 member
,而且根本没有调用字符串的构造函数。它甚至不是默认构造的。它的内部状态是完全未定义的。所以程序会被简单地破坏。
重要的是异常的传播方式可以避免像这样的混乱。
先发制人:请记住,初始化列表之所以重要,首先是为了让成员变量可以直接初始化,而无需调用其默认构造函数事先.