try..catch块为什么需要花括号?

50

尽管在其他语句(比如if...else)中,如果块中只有一条指令,您可以避免使用大括号,但在try...catch块中,您不能这样做:编译器不接受这种写法。例如:

try
    do_something_risky();
catch (...)
    std::cerr << "Blast!" << std::endl;

使用以上代码,g++只是简单地表示它在do_something_risky()之前期望一个'{'。为什么try...catch和if...else之间会有这种行为差异呢?

谢谢!


13
因为规格书就是这么写的。 - Justin Niessner
2
因为设计师决定的原因。我们总是一致地做事吗?比如变量命名,文件命名等。 - Kevin Le - Khnle
3
曾经的try/catch是执行setjmp/longjmp的宏。也许这就是原因? - Paul Tomblin
2
@Paul,在标准C++中它们不是这样的。 - anon
20
一个好问题。由于我使用括号来包含所有块,我从未意识到这一点。回答“那是语法规定”的人并没有真正回应问题。 - anon
显示剩余4条评论
9个回答

14

直接来自C++标准规范:

try-block:
    try compound-statement handler-seq

正如你所看到的,所有的try-block都期望一个compound-statement。根据定义,复合语句是用花括号包含的多个语句。

将所有内容都放在一个复合语句中确保为try-block生成了新的作用域。在我看来,这也使得所有内容稍微更易于阅读。

你可以在C++语言规范的第359页上自行验证。


22
恕我直言,这个回答没有帮助。标准需要使用大括号是显而易见的。问题不是“try-catch块需要用大括号吗”,而是“为什么需要用...”。 - Catskul
13
在一个复合语句中拥有所有内容可以确保为 try 块生成新的作用域。C++ 中的作用域不依赖于花括号。此外,在 if (n > 0) std::vector<float> v(n); 中,在右括号后打开一个新作用域(仅包含 v),并在分号处结束。 - 5gon12eder

10

try块的语法如下:

try compound-statement handler-sequence

handler-sequence 是一个或多个处理程序组成的序列,具有以下语法:


catch (type-specifier-seq declarator) compound-statement
catch (...) compound-statement

这与其他控制语句(如if、while、for等)不同。 这些语句的语法如下:

if (condition) statement-true else statement-false  
while (condition) statement
for (init-statement; condition; iteration_expression) statement
etc.

现在的问题是为什么try块中需要复合语句而不是单个语句?

考虑以下代码:

int main()
{
  // before try-statement.

  try g(); catch (std::runtime_error e) handleError(e);

  // after try-statement.
}

我知道,按值捕获是一种不好的做法(例如可能存在对象切片等问题),但是为了防止关于异常存储期的讨论并使推理更加容易,我这样做了。

现在考虑一下'e'的存储期和链接。您所期望的是,在调用handleError函数之前只能引用'e',但在调用完成后不能引用。它应该具有此“范围”内的自动存储期和无链接。这可能可以通过隐式定义类似其他语句中的局部范围来实现,但将异常声明看起来像函数参数可能是一个更好的想法。因此需要块(复合语句)。请看下面。

现在考虑一下try和之后的语句。没有理由在那里使用try关键字,也没有理由使用复合语句,但语法可能会变得模糊和复杂。

这就是Stroustrup在《C++异常处理》中对此的看法:Exception Handling for C++

It might be possible to simplify the

try { ... } catch (abc) { ... }

syntax  by  removing  the  apparently  redundant try keyword,
removing  the  redundant  parentheses, and by allowing a handler
to be attached to any statement and not just to a block.  For 
example, one might allow:

void f()
{
  g(); catch (x1) { /* ... */ }
}

as an alternative to - 28 -

void f()
{
  try { g(); } catch (x1) { /* ... */ }
}

The added notational convenience seems insignificant and may not
even be convenient. People seem to prefer syntactic constructs that
start with a prefix that alerts them to what is going on, and it may
be easier to generate good code when the try keyword is required.  

并在更详细的解释之后:

Allowing exception handlers to be attached to blocks only and not to
simple statements simplifies syntax analysis (both for humans and
computers) where several exceptions are caught and where nested
exception  handlers are considered (see Appendix E). For example,
assuming that we  allowed handlers to be attached to any statement
we could write:

try try f(); catch (x) { ... } catch (y) { ... } catch (z) { ... }

The could be interpreted be in at least three ways:

try { try f(); catch (x) { ... } } catch (y) { ... } catch (z) { ... }
try { try f(); catch (x) { ... } catch (y) { ... } } catch (z) { ... }
try { try f(); catch (x) { ... } catch (y) { ... } catch (z) { ... } }

There seems to be no reason to allow these ambiguities even if there
is a trivial and systematic way for a parser to chose one
interpretation over another. Consequently, a { is required after a
try and a matching } before the first of the associated sequence of
catch clauses.

正如Stroustrup所说,没有花括号,语句的含义可能会因规则而异,您可能需要加上花括号来澄清意图。我们能否像Stroustrup的示例那样使用if语句创建一些复杂的语句呢?当然可以,例如:

if (c1) if (c2) f(); else if (c3) g(); else h();

这实际上等同于:

if (c1) { if (c2) f(); else { if (c3) g(); else h(); } }

但我认为这比try块的情况少了问题。if语句有两种语法:

if (condition) statement-true
if (condition) statement-true else statement-false
因为有时候没有else动作是合理的。但是一个没有catch子句的try块是没有意义的。可以省略“try”,但不实用,正如Stroustrup所说,但如果你指定了try块,则不能省略catch子句。除此之外,可能会有多个与相同try块相关的catch,但只有一个基于异常类型和catch子句的顺序规则被执行。

现在,如果if-else的语法改变为:

if (condition) compound-statement-true else compound-statement-false

然后,你必须像这样编写if-else:

if (c1) { f(); } else { if (c2) { g(); } else { h(); } }

注意到没有'elseif'关键字,也没有'else if'的特殊语法。我认为即使是那些坚持「始终加大括号」的人也不喜欢这样写,而会写成这样:

if (c1) { f(); } else if (c2) { g(); } else { h(); }

我认为这不是将语法定义为上述形式并引入“elseif”关键字或为“else if”定义特殊语法的充分理由。


2
这些都不是很有说服力。最后一条引语中的示例与 if-else 的情况没有区别:else 跳到最近的 if,如果要更改 - 使用 {}。我不明白为什么这个规则不能被应用到 try-catch 中。 - AnT stands with Russia
斯特劳斯特鲁普的引言非常准确,而其他文本则不尽如此。 - Remember Monica

10

不确定为什么,但其中一个好处是没有悬挂捕获问题。请参见悬挂else问题,了解在可选括号时可能出现的歧义。


6
请阅读此链接。大多数原因似乎与在实际异常情况下需要创建和销毁的对象的范围和分配有关。
因此,我猜测C++的语法编写者正在要求g ++(或任何符合标准的C++编译器)的作者为最坏情况做好准备,而g ++的作者似乎已经这样做了。

2
这个链接似乎已经失效了。 - Craig McQueen

6

为什么?安全性和向后兼容之间的权衡。

从if...else中学到的教训表明,需要花括号可以消除错误。现在,ISO C++人员强烈倾向于与C向后兼容,因此他们没有改变if...else的C语法。但是,新的结构需要花括号来标记受控块,因为它们不会出现在旧的C代码中,因此向后兼容性不是一个问题。


3
如果这是原因,那么我要感谢向后兼容性让我能够使用"if () if () else ;"结构。我喜欢这些东西,也很有能力适当地使用它们。一个人当然有可能做到这一点。(当然,在C++的精神中,无论实际的程序员有多么能干,都会给他足够的绳子去绞死自己;因此,如果确实是这种情况造成了try-catch,我会有些惊讶。) - mjwach

3

首先,这是语法的工作原理。

其次,我相信目标是强制为异常块生成一个新的范围(如果我错了,请纠正我)。


1

这就是他们想要的方式。没有任何理由,这是一条法律。


1

不确定您是否在使用.NET,但CLR使用大括号作为标志。

http://dotnet.sys-con.com/node/44398

从文章中可以看出:“SEH(结构化异常处理)表由一组子句组成,描述了受保护代码的结构。该表具有一组二进制标志,用于描述异常处理子句的类型:Try Offset标志,表示受保护代码块的开头;Try Length标志,表示受保护代码的长度;Handler Offset和Handler Length标志,详细说明异常处理程序块的开头及其长度;以及一个Class Token或Filter Offset标志,具体取决于定义的异常处理程序类型。这些信息允许CLR在发生异常时确定要执行的操作。它映射了受保护代码块的开头、异常处理程序要执行的代码以及与过滤或其他特殊情况相关的特殊语义。”
我认为其他框架也会做同样的事情。

-1

主要是因为

if (a)
    int b = 10;
else 
    int b = 5;
b += 5;

会失败,因为没有 {} 的 if...else 是这种语法的快捷方式

if (a) {
    int b = 10;
} else {
    int b = 5;
}
b += 5;

这明确告诉你,int b与软件的其余部分处于不同的作用域。

如果我没记错,以下内容也会失败。

a ? int b = 10 : int b = 5;
b += 5;

尽管您的编译器可能会为您优化该代码... 但由于 if/else 语句中的作用域,它在技术上应该会失败。

每当您看到 {},您都在定义软件的作用域。

-Stephen


2
关于作用域的观点很好,但这并没有回答问题(或者可能是我没有理解你的观点)。 - nico
2
每个if、else if和else块都定义了自己的作用域,无论您是否使用大括号,因此我认为您不需要try或catch块来使用大括号来确保它们有自己的作用域。所以,我不认为作用域问题会回答这个问题——尽管显然您希望try和catch块具有自己的作用域。 - Jonathan M Davis
我的评论是所有从句都强制定义作用域。没有 {} 的 If/Else 语句是遗留组件,当代码逐行编写时使用的语法快捷方式 - 它们是将这些从句放入其中的语法快捷方式。您应该始终像第二个示例那样编码。像 try/catch 这样的东西需要 {} 的原因是它们没有需要支持的遗留等效项。 - Stephen Furlani
笑。不是要冒犯,但有很多人会非常不同意你应该总是在if/else语句或循环中使用大括号。当然,有些人(显然包括你在内)认为这是好的风格,但就像人们会争论最好的大括号放置位置一样,他们也会争论是否应该总是在if/else语句中使用大括号。如果不需要,我会反对它。两种风格都有好的理由,但主要取决于个人喜好和适合自己的方法。 - Jonathan M Davis

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