主函数周围没有花括号-为什么这样可以工作?

28

我正在学习一本C++书籍,其中有一章讲解错误处理,这是该章节中的一个程序(我省略了一些细节但主要是这个):

int main()
try { 
        // our program (<- this comment is literally from the book)
        return 0;
}
catch(exception& e) {
    cerr << "error: " << e.what() << '\n';
    return 1;
}
catch(...) {
    cerr << "Unknown exception\n";
    return 2;
}

这段代码编译通过了,但它什么也没做,所以我仍在思考:

  1. 为什么在 main() 函数后面没有一组花括号括起来?这些块,或者说 "catchphrases"(哈哈),是不是 main() 函数的一部分?
  2. 如果它们是函数,那么 catch(whatever) 前面为什么没有 "int"?
  3. 如果它们不是函数,那它们是什么?
  4. 关于 catch(...),我从未见过省略号被这样使用。我可以在任何地方使用省略号来表示 "任何东西" 吗?

2
catch (...) 可以捕获任何异常。至于一般情况下,可以查看 C++ 中的异常处理(其他语言的语法也非常相似)。 - chris
哇,我本来要写“你用什么疯狂的老编译器才能接受这个?”但是后来在g++ 4.5.3中测试了一下,惊讶地发现它竟然编译通过了... - Adam Rosenfield
2
哦,对了,函数 try 块。我记得这个是来自于那些 Crazy corners of C++ 问题之一。 - chris
1
https://dev59.com/5WbWa4cB1Zd3GeqPVla_#11576306 - user166390
1
请查看http://www.drdobbs.com/introduction-to-function-try-blocks/184401297以获取更详细的解释。 - Adam Rosenfield
显示剩余3条评论
6个回答

16
如果你直接使用 try 块而不是花括号,try 块会捕获函数中发生的所有异常。这在构造函数中非常有用,因为你可以在初始化列表中捕获异常。
这将捕获 bar 构造函数引发的异常:
void Foo::Foo() try : bar() {
    // do something
} catch(...) {
    // do something
}

这只会捕获代码块中的异常:

void Foo::Foo() : bar() {
    try {
        // do something
    } catch(...) {
        // do something
    }
}

4
请注意,构造函数中的使用是有限制的。在 catch 块结束之前,构造函数必须抛出异常(或重新抛出),在一般情况下几乎不可能执行任何清理工作。 - David Rodríguez - dribeas

12

1 为什么在main()后面没有一组花括号把所有东西都包起来呢?...

实际上有的,只是在左花括号之前有关键字try,而在main结束之后有一些catch

... 这些块或者我应该称它们为“catch语句”(哈!)是否是main()函数的一部分?

2 如果它们是函数,那么为什么在catch(whatever)之前没有“int”?

3 如果它们不是函数,那它们是什么?

它们是关键字而不是函数,尽管try出现在int main()的定义和其{}主体之间。请参见以下的激励案例以了解另一个示例。

4 关于catch(...),我从未见过省略号被用于这种方式。 我可以随处使用省略号表示“任何”吗?

在C++中有几个重载的省略号含义:

  • catch(...) 表示捕获任何异常,它是异常类型的通配符(如果有多个,则应该是最后一个catch)。
  • int printf(char*, ...) 表示函数接受一个可变参数列表,这会完全禁用参数的类型检查,并容易出错(但偶尔很有用)。
  • template <typename... TypePack> 表示模板接受一个可变类型列表,这对于元编程非常有用,但超出了此处的范围。
  • #define DEBUG(str, ...) 是变参宏,类似于可变参数函数。

函数级别下的try/catch块是一种将整个函数体包装在异常处理程序中的方法。因此,在此处,main函数块位于try { ... }内。

据我所知,引入这个特性主要是为了允许构造函数使用try/catch来包装其初始化器列表,以处理从子对象构造函数中抛出的异常。

例如(激励案例):

C::C() try : Base(1), member(2)
{ ...
}
catch (...) {
    // handle Base::Base or member construction failure here
}

请注意,如果你忽略初始化列表,在基类或成员子对象构造函数中抛出异常是没有其他方式可以捕获的,因为它们始终会在你的构造函数体开始之前执行至少默认构造。


10

那是语言中很少使用的功能。你可以将整个函数放在 try catch 块中:

void f()
try 
{         // function starts here
...
}         // function ends here
catch (...) 
{}

这个特性很少使用,因为它几乎从来没有用处。在普通函数的情况下,您可以只用一个try/catch块覆盖函数的所有代码,所以在这种情况下就不需要该特性了。它在构造函数中的功能受到限制,因为它允许捕获初始化列表中无法被try/catch块包围的异常。

但问题在于,在catch块中几乎不能执行任何清理操作:构造失败,因此必须抛出异常(catch块可以抛出与被捕获的异常不同的异常,但它必须抛出异常)。同时,您也不能真正进行任何清理,因为在捕获异常的时候,你可能不知道哪些元素在初始化列表中抛出了异常,这意味着你可能不知道(在一般情况下)哪些成员对象已经构造或未构造。


2

这是一个“函数尝试块”。是的,它是合法的,但在大多数情况下并不需要。查看此答案以获取解释。这个语法的含义是什么?


2

关于主函数周围缺少花括号的问题,不确定,但看起来其他人已经回答了。

至于你的其余问题,try catch块不是主函数外的函数,也不需要前面有int

try {}:程序将尝试运行此块中的任何内容,如果由于任何原因失败,它将被catch块捕获。

catch (exception) {}:当抛出异常时,它将被具有特定Exception作为参数的catch块捕获,并允许程序员处理该情况。

catch (...) {}:应该捕获通过第一个catch块的所有内容..


0

1) 这是C++的一个特性,函数try-catch块(例如请参见这里
2) 是的,catch(...) { /* 绝对任何异常都会在这里被捕获 */ }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接