在我看来,如果你有一些像这样的 C++ 代码:
int f()
{
try {
if( do_it() != success ) {
throw do_it_failure();
}
} catch( const std::exception &e ) {
show_error( e.what() );
}
}
C++ 编译器应该能够将 throw 和 catch 优化为几乎简单的 goto。
但是,根据我查看反汇编和单步执行代码的经验,编译器总是跳过非常混乱的异常处理库。
他们为什么要这么做?是否存在某些阻碍优化的语言要求?如果是的话怎么办:
int f()
{
try { throw std::runtime_error("Boo!"); }
catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
}
为什么编译器不直接将其重写为
int f()
{
std::cout << "Boo!" << std::endl;
}
我认为所接受的答案即使没有错误,也是相当缺乏信息的,所以即使过了这么多年,我仍然觉得有必要提供一个正确的答案。
猜测为什么编译器实现者选择不在任何特定功能上投入精力只是……猜测。仅在特殊情况下抛出异常这一事实通常不会被认为是不优化此类代码性能的理由。相反,尽管抛出代码确实没有以牺牲非抛出代码为代价进行优化,但异常抛出和处理基础设施仍然经过了非常仔细的优化。 此外,这段代码可能感觉很做作,不值得考虑,但事实并非如此:它可能是由于内联和优化更复杂的代码而产生的,优化它可能会产生更简单的代码,从而允许其他优化传递fire,或者要进一步内联的包含函数。像这样的优化过程,当正确且高效地实现时,总是值得至少被考虑,无论原始代码看起来多么做作。否则,即使像“死代码消除”这样的基本步骤也会被避免,因为“死代码不应该首先编写”。显然事实并非如此。
因此我只是不同意已接受的答案。应该异常抛出异常这一事实不是此代码未优化的原因。
原因纯粹是技术性的,clang 开发邮件列表中的这封电子邮件对此进行了解释:http://lists.llvm.org/pipermail/cfe-dev/2015-March/042035.html
总而言之,该语言允许在catch
块内调用的代码在任何“从未见过”异常对象的情况下“重新抛出”异常:
void g() { throw; }
因此,考虑OP代码:
int f()
{
try { throw std::runtime_error("Boo!"); }
catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
}
对于编译器而言,e.what()
或两次调用operator<<
确保情况并非如此,需要“整个程序知识”,如上面的电子邮件中所写。甚至更简单的情况
也可以
进行优化,例如:
int func() {
try {
throw 42;
}catch(int x) {
return x;
}
}
上面的代码可以转化为return 42
尽管如此,大多数常见的编译器都不会这样做(
godbolt)。这次,我们可以从实际来源(上面链接的电子邮件)看出,Clang 开发人员(我们不能对其他编译器说什么)认为这种优化不值得,可能
,因为它只适用于
catch
块不进行函数调用。 因为在抛出
do_it()
之前,
do_it_failure();
他们为什么要这么做?
因为 C++ 异常适用于“异常”情况,并且异常情况下的性能并不重要。
从一开始就鼓励用户仅在特殊情况下使用异常,并鼓励实现者优化无异常情况(析构函数地址必须存储在某处,以便在异常发生时调用析构函数),但代价是特殊情况。
虽然实施者当然可以花费资源来优化奇怪的例外情况,但大多数用户不会喜欢这样,因为总是有很多更重要的事情需要改进。