Почему блоки try..catch требуют фигурных скобок? - PullRequest
43 голосов
/ 09 июня 2010

В то время как в других операторах, таких как if ... иначе вы можете избежать скобок, если в блоке есть только одна инструкция, вы не можете сделать это с помощью блоков try ... catch: компилятор не покупает ее. Например:

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

С приведенным выше кодом g ++ просто говорит, что ожидает '{' перед do_something_risky (). Почему такая разница в поведении между try ... catch и, скажем, если ... else?

Спасибо!

Ответы [ 9 ]

13 голосов
/ 09 июня 2010

Прямо из спецификации C ++:

try-block:
    try compound-statement handler-seq

Как видите, все try-block s ожидают compound-statement. По определению составной оператор - это несколько операторов, заключенных в фигурные скобки.

Наличие всего в составном операторе обеспечивает создание новой области действия для блока try. Это также делает все немного легче читать на мой взгляд.

Вы можете проверить это самостоятельно на странице 359 спецификации языка C ++

10 голосов
/ 09 июня 2010

Не знаю почему, но одно преимущество заключается в том, что нет проблемы с висящим уловом.См. dangling-else для двусмысленности, которая может возникнуть, когда фигурные скобки являются необязательными.

7 голосов
/ 13 октября 2016

Синтаксис try-block:

try compound-statement handler-sequence

где handler-sequence - это последовательность одного или нескольких обработчиков, которые имеют следующий синтаксис:

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

Это отличается от других операторов, таких как операторы управления (если, 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'. Что вы ожидаете, так это то, что «e» можно ссылаться только перед вызовом функции handleError, но не после завершения вызова. Он должен иметь автоматическую продолжительность хранения и не иметь связи в этой «области видимости». Вероятно, это можно сделать, неявно определив локальную область видимости, как в других операторах, но сделать объявление-исключение похожим на параметр функции, вероятно, было лучшей идеей. Таким образом, блок (составной оператор) необходим. Se ниже.

Теперь подумайте о попытке и утверждении после этого. Там нет никаких оснований для использования ключевого слова try и нет причин для использования составного оператора, но синтаксис может стать двусмысленным и сложным.

Вот что сказал об этом Страуструп в Обработка исключений для 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.

Как сказал Страуструп, без фигурных скобок утверждение может означать разные вещи в зависимости от правила, и вам, вероятно, понадобится поставить фигурные скобки, чтобы уточнить намерение. Можем ли мы сделать что-то, что выглядит сложным с помощью оператора if, как в примере Страуструпа? Конечно, мы можем, что-то вроде этого, например:

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

Это фактически эквивалентно:

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

Но я думаю, что это менее проблематично, чем в случае try-block. Существует два синтаксиса для if-утверждения:

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

потому что иногда имеет смысл не делать других действий. Но нет смысла в блоке try без предложения catch. «Пытаться» можно опустить, но это непрактично, как сказал Страуструп, но предложение catch нельзя, если вы указали блок try. Помимо этого, может быть более одного улова, связанного с одним и тем же блоком try, но только один выполняется на основе правил, которые зависят от типа исключения и порядка выражений 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 если.

6 голосов
/ 09 июня 2010

Читать по этой ссылке . Большая часть причин, по-видимому, связана с управлением областью действия и распределением объектов, которые необходимо создавать и уничтожать в случае реальных исключений.

Итак, я предполагаю, что авторы грамматики C ++ просят авторов g ++ (или любого стандарта, соответствующего компилятору C ++) подготовить его для наихудших возможных случаев, и авторы g ++, похоже, сделали это.

5 голосов
/ 10 июня 2010

Почему?Компромисс между безопасностью и обратной совместимостью.

Уроки, извлеченные из if ... else, показали, что использование фигурных скобок устраняет ошибки.Теперь сотрудники ISO C ++ предпочитают обратную совместимость с C, поэтому они не изменили синтаксис C для if ... else.Но для новых конструкций требуются фигурные скобки для разграничения управляемых блоков, так как они не будут появляться в старом коде C и, следовательно, обратная совместимость не имеет значения.

3 голосов
/ 09 июня 2010

Ну, во-первых, так работает грамматика.

Во-вторых, я считаю, что цель состоит в том, чтобы принудительно создать новую область видимости для блоков исключений (поправьте меня, если я ошибаюсь).

1 голос
/ 09 июня 2010

Не уверен, что вы используете .NET, но CLR использует фигурные скобки в качестве флагов.

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

Из статьи: «Таблица SEH (обработка исключений структуры) состоит из набора предложений, которые описывают структуру защищенного кода. В таблице есть набор двоичных флагов, которые описывают тип предложения обработки исключений: a TryФлаг смещения, который является началом блока защищенного кода, флаг Try Length, который является длиной защищенного кода; флаги смещения обработчика и длины обработчика, которые детализируют начало блока обработчика исключений и его длину;и токен класса или флаг смещения фильтра, в зависимости от типа обработчика исключений, который был определен. Эта информация позволяет CLR определять, что делать в случае возникновения исключения. Он отображает начало блока защищенного кода, код для выполненияза исключением и особой семантики, связанной с фильтрацией или другими специальными обстоятельствами. "

Я бы предположилчто другие фреймворки делают то же самое.

1 голос
/ 09 июня 2010

Вот как они хотели быть.Нет оправдания, это закон.

0 голосов
/ 10 июня 2010

В основном это потому, что

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

...