Когда функция пытается использовать блок?

Мне интересно, когда программисты используют функциональные блоки try. Когда это полезно?

void f(int i)
try
{
 if ( i < 0 ) 
 throw "less than zero";
 std::cout << "greater than zero" << std::endl;
}
catch(const char* e)
{
 std::cout << e << std::endl;
}
int main() {
 f(1);
 f(-1);
 return 0;
}

Выход: (at ideone)

greater than zero
less than zero

EDIT: Как некоторые люди могут подумать, что синтаксис функции defintion неверен (поскольку синтаксис не выглядит привычным), я должен сказать, что нет его неправильного. Его называют функцией-try-block. См. §8.4/1 [dcl.fct.def] в стандарте С++.

7 ответов

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

В противном случае это бесполезно: если я не ошибаюсь,

void f() try { ... } catch (...) { ... }

строго эквивалентно

void f() { try { ... } catch (...) { ... } }


Функция try block полезна для меня в двух контекстах.

a) Чтобы иметь предложение catch all main(), позволяющее писать небольшие утилиты, не беспокоясь о локальной обработке ошибок:

int main()
try {
 // ...
 return 0;
}
catch (...) {
 // handle errors
 return -1;
}

который явно является синтаксическим сахаром, для того, чтобы попробовать/поймать внутри main().

b) обрабатывать исключения, создаваемые конструкторами базового класса:

struct B {
 B() { /*might throw*/ }
};
struct A : B {
 A() 
 try : B() { 
 // ... 
 } 
 catch (...) {
 // handle exceptions thrown from inside A() or by B() 
 } 
};


Помимо упомянутых функциональных применений, вы можете использовать функцию-try-block, чтобы сохранить один уровень отступов. (Ack, ответ о стилях кодирования!)

Обычно вы видите примеры с функцией-try-block следующим образом:

void f(/*...*/)
try {
 /*...*/
}
catch(/*...*/) {
 /*...*/
}

Если область видимости функции имеет отступы на том же уровне, что и при отсутствии функции-try-block. Это может быть полезно, если:

  • у вас есть ограничение на 80 символов и вам придется обернуть строки с учетом дополнительного отступа.
  • вы пытаетесь модифицировать некоторую существующую функцию с помощью try catch и не хотите касаться всех строк функции. (Да, мы могли бы просто использовать git blame -w.)

Хотя для функций, которые полностью завернуты с помощью функции-try-block, я бы предложил не чередовать некоторые функции с помощью функций-try-блоков, а некоторые не в одной и той же базе кода. Консистенция, вероятно, более важна, чем проблемы с переносом строки. :)


Может быть полезно, если вы хотите поймать исключения из инициализатора конструктора.

Однако, если вы делаете исключение в конструкторе таким образом, вам нужно либо перебросить его, либо выбросить новое исключение (т.е. вы не можете просто нормально вернуться из конструктора). Если вы не свернете, это произойдет неявно.

#include <iostream>
class A
{
public:
 A()
 try {
 throw 5;
 }
 catch (int) {
 std::cout << "exception thrown\n";
 //return; <- invalid
 }
};
int main()
{
 try {
 A a;
 }
 catch (...) {
 std::cout << "was rethrown";
 }
}
</iostream>


Заметки о том, как работают функции try block:

  • Для конструкторов блок try функции включает в себя построение элементов данных и базовых классов.

  • Для деструкторов блок try функции включает в себя уничтожение элементов данных и базовых классов. Это усложняется, но для С++ 11 вы должны включить noexcept(false) в объявление вашего деструктора (или класса базы/члена) или любое исключение уничтожения, что приведет к завершению в конце блока catch. Возможно, это можно предотвратить, поставив оператор return в блок catch (но это не будет работать для конструкторов).

  • Блок catch в конструкторе или деструкторе должен выдать какое-то исключение (или он будет неявно перебросить пойманное исключение). Нелегко просто return (по крайней мере, в блоке catch функции конструктора). Обратите внимание, однако, что вы можете вызвать exit() или подобное, что может иметь смысл в некоторых ситуациях.

  • Блок catch не может вернуть значение, поэтому он не работает для функций, возвращающих не-void (если они не намеренно завершают программу с помощью exit() или аналогичного). По крайней мере, это то, что я прочитал.

  • Блок catch для функции-функции-try не может ссылаться на данные/базовые элементы, так как они будут либо иметь 1) не были сконструированы, либо 2) уничтожены до улова. Таким образом, функциональные блоки try не полезны для очистки внутреннего состояния объекта - объект должен быть полностью "мертв" к тому времени, когда вы туда доберетесь. Этот факт очень опасен для использования блоков функций try в конструкторах, поскольку с течением времени сложно защитить это правило, если ваш компилятор не случайно его флаг.

действительный (юридический) использует

  • Перевод исключения (к другому типу/сообщению), созданного во время конструктора или его конструкторов base/member.
  • Несмотря на то, что во время деструктора или его деструкторов-деструкторов (или деструкторного этикета) было исключено исключение и исключение.
  • Завершение работы программы (возможно, с полезным сообщением).
  • Какая-то схема регистрации исключений.
  • Синтаксический сахар для функций, возвращающих пустоты, которые нуждаются в полностью инкапсулирующем блоке try/catch.


Нет, нет. В этом тривиальном случае это не полезно. Однако полезно инкапсулировать ctor-initialisers в try/catch (если ваш дизайн когда-либо достаточно сломан, чтобы потребовать его).


Еще одна вещь, для которой вы можете использовать их, - предоставить дополнительные данные во время отладки, чтобы не мешать готовой сборке. Я не видел, чтобы кто-либо использовал или защищал его, но это то, что мне удобно.

// Function signature helper.
#if defined(_WIN32) || defined(_WIN64)
 #define FUNC_SIG __FUNCSIG__
#elif defined(__unix__)
 #define FUNC_SIG __PRETTY_FUNCTION__
// Add other compiler equivalents here.
#endif /* Function signature helper. */
void foo(/* whatever */)
#ifdef DEBUG
try
#endif /* DEBUG */
{
 // ...
}
#ifdef DEBUG
catch(SomeExceptionOrOther& e) {
 std::cout << "Exception " << e.what() << std::endl
 << "* In function: " << FUNC_SIG << std::endl
 << "* With parameters: " << /* output parameters */ << std::endl
 << "* With internal variables: " << /* output vars */ << std::endl;
 throw;
}
#endif /* DEBUG */

Это позволит вам получить полезную информацию при тестировании вашего кода и легко заглушить ее, не затрагивая ничего.

licensed under cc by-sa 3.0 with attribution.