Когда функция пытается использовать блок?
Мне интересно, когда программисты используют функциональные блоки 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] в стандарте С++.
Ответы
Ответ 1
Вы используете его в конструкторах для улавливания ошибок из инициализаторов. Обычно вы не поймаете эти ошибки, поэтому это весьма исключительное использование.
В противном случае это бесполезно: если я не ошибаюсь,
void f() try { ... } catch (...) { ... }
строго эквивалентно
void f() { try { ... } catch (...) { ... } }
Ответ 2
Функция 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()
}
};
Ответ 3
Помимо упомянутых функциональных применений, вы можете использовать функцию-try-block, чтобы сохранить один уровень отступов. (Ack, ответ о стилях кодирования!)
Обычно вы видите примеры с функцией-try-block следующим образом:
void f(/*...*/)
try {
/*...*/
}
catch(/*...*/) {
/*...*/
}
Если область видимости функции имеет отступы на том же уровне, что и при отсутствии функции-try-block. Это может быть полезно, если:
- у вас есть ограничение на 80 символов и вам придется обернуть строки с учетом дополнительного отступа.
- вы пытаетесь модифицировать некоторую существующую функцию с помощью try catch и не хотите касаться всех строк функции. (Да, мы могли бы просто использовать
git blame -w
.)
Хотя для функций, которые полностью завернуты с помощью функции-try-block, я бы предложил не чередовать некоторые функции с помощью функций-try-блоков, а некоторые не в одной и той же базе кода. Консистенция, вероятно, более важна, чем проблемы с переносом строки.
:)
Ответ 4
Может быть полезно, если вы хотите поймать исключения из инициализатора конструктора.
Однако, если вы делаете исключение в конструкторе таким образом, вам нужно либо перебросить его, либо выбросить новое исключение (т.е. вы не можете просто нормально вернуться из конструктора). Если вы не свернете, это произойдет неявно.
#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";
}
}
Ответ 5
Заметки о том, как работают функции 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.
Ответ 6
Нет, нет. В этом тривиальном случае это не полезно. Однако полезно инкапсулировать ctor-initialisers в try/catch (если ваш дизайн когда-либо достаточно сломан, чтобы потребовать его).
Ответ 7
Еще одна вещь, для которой вы можете использовать их, - предоставить дополнительные данные во время отладки, чтобы не мешать готовой сборке. Я не видел, чтобы кто-либо использовал или защищал его, но это то, что мне удобно.
// 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 */
Это позволит вам получить полезную информацию при тестировании вашего кода и легко заглушить ее, не затрагивая ничего.