Использование catch (...) (эллипсис) для посмертного анализа

Кто-то в другом вопросе предложил использовать catch(...) для захвата всех других необработанных неожиданных/непредвиденных исключений, окружая весь main() блоком try{}catch(...){}.

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

Суть вопроса в том, что какая информация может быть восстановлена ​​ таким образом (отличные от всех отладочных глобальных переменных, которые я оставляю позади), и как восстановить его (как получить доступ и узнайте, какой вызов был вызван)

Кроме того, какие предостережения связаны с ним. В частности:

  • будет ли он играть хорошо с потоками, которые прорастают позже?
  • не будет ли он нарушать обработку segfaults (захвачен где-то в качестве сигнала).
  • не повлияет ли он на другие блоки try..., которые неизбежно вложены внутри, которые должны обрабатывать ожидаемые исключения?

Ответ 1

Да, это хорошая идея.

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

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

int main()
{
    try
    {
        /// All real code
    }
    // I see little point in catching other exceptions at this point 
    // (apart from better logging maybe). If the exception could have been caught
    // and fixed you should have done it before here.

    catch(std::exception const& e)
    {
         // Log e.what() Slightly better error message than ...
         throw;
    }
    catch(...)   // Catch all exceptions. Force the stack to unwind correctly.
    {
        // You may want to log something it seems polite.
        throw;  // Re-throw the exception so OS gives you a debug opportunity.
    }
}
  • будет ли он играть хорошо с потоками, которые прорастают позже?

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

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

  • не будет ли он нарушать обработку segfaults (захвачен где-то в качестве сигнала).

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

  • не повлияет ли он на другие блоки try..., которые неизбежно вложены внутри, которые должны обрабатывать ожидаемые исключения?

Не должно влиять на другие обработчики.

Ответ 2

Насколько я помню, catch(...) на Win32 ловит также исключения SEH, а вы не хотите делать это. Если вы получаете исключение из SEH, потому что произошло что-то очень страшное (в основном нарушения доступа), поэтому вы больше не можете доверять своей среде. Почти все, что вы могли бы сделать, может потерпеть неудачу с другим исключением SEH, поэтому его даже не стоит пытаться. Кроме того, некоторые исключения SEH предназначены для захвата системой; подробнее об этом здесь.

Итак, мой совет - использовать базовый класс исключений (например, std::exception) для всех ваших исключений и поймать только этот тип в "catchall"; ваш код не может быть подготовлен к рассмотрению других видов исключений, поскольку они по определению неизвестны.

Ответ 3

Глобальный блок catch try полезен для производственных систем, чтобы избежать отображения неприятного сообщения пользователю. Во время разработки я считаю, что лучше избегать.

Относительно ваших вопросов:

  • Я считаю, что глобальный блок catch не будет перехватывать исключения в другом потоке. Каждый поток имеет собственное пространство стека.
  • Я не уверен в этом.
  • Вложенные блоки try... catch не затрагиваются и будут выполняться как обычно. Исключение распространяется на стек, пока не найдет блок try.

Ответ 4

Вы можете попробовать решение, которое я использую, если вы создаете приложение .net. Это фиксирует все необработанные исключения. Я обычно включаю только код (с #ifndef DEBUG) для производственного кода, когда я не использую отладчик.

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

Ответ 5

и как его восстановить (как получить доступ и признать, что называется с)

Если вы имеете в виду, как восстановить тип исключенного исключения, вы можете связать блоки блокировки для определенных типов (исходя из более специфических для более общих), прежде чем вернуться к catch (...):

try {
   ...
} catch (const SomeCustomException& e) {
   ...
} catch (const std::bad_alloc& e) {
   ...
} catch (const std::runtime_error& e) {
   // Show some diagnosic for generic runtime errors...
} catch (const std::exception& e) {
   // Show some diagnosic for any other unhandled std::exceptions...
} catch (...) {
   // Fallback for unknown errors.
   // Possibly rethrow or omit this if you think the OS can do something with it.
}

Обратите внимание: если вы обнаружите, что делаете это в разных местах и ​​хотите консолидировать код (возможно, несколько функций main для отдельных программ), вы можете написать функцию:

void MyExceptionHandler() {
   try {
      throw; // Rethrow the last exception.
   } catch (const SomeCustomException& e) {
      ...
   }
   ...
}

int main(int argc, char** argv) {
   try {
      ...
   } catch (...) {
      MyExceptionHandler();
   }
}

Ответ 6

Уловка не будет очень полезной, поскольку нет информации о типе/объекте, которую вы можете запросить. Однако, если вы можете убедиться, что все исключения, вызванные вашим приложением, получены из одного базового объекта, вы можете использовать блок catch для базового исключения. Но тогда это не было бы уловкой.