Правильно завершающая программа. Использовать исключения


Вопрос: Является ли использование исключений правильным способом завершить работу моей программы, если все, что я хочу, это вывести сообщение об ошибке и закрыть (учитывая, что я могу быть глубоко в программе)? Могу ли я просто явно вызвать что-то вроде exit() вместо этого?

Что я сейчас делаю:

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

В настоящее время я делаю это с исключениями, такими как:

int main()
{
   Game game;
   try
   {
       game.run();
   }
   catch (BadResolutionException & e)
   {
       Notification::showErrorMessage(e.what(), "ERROR: Resolution");
       return 1;
   }
   catch (BadAssetException & e)
   {
       Notification::showErrorMessage(e.what(), "ERROR: Assets");
       return 1;
   }
   catch (std::bad_alloc & e)
   {
       Notification::showErrorMessage(e.what(), "ERROR: Memory");
       return 1;
   }
   return 0;
}

Все, кроме bad_alloc, являются моими собственными определенными исключениями, производными от runtime_error.

Мне не нужна ручная очистка ресурсов,и я использую std:: unique_ptr для любого динамического выделения. Мне просто нужно вывести сообщение об ошибке и закрыть программу.

Исследования / альтернативы исключениям:

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

Использование exit () звучит неплохо, но я читал, что он не будет возвращаться через стек вызовов до main, очищая все (если я смогу найти это снова, я опубликую ссылку). Кроме того, согласно http://www.cplusplus.com/reference/cstdlib/exit/ это не должно использоваться, если несколько потоки активны. Я ожидаю, что буду создавать второй поток в течение короткого времени, по крайней мере, один раз, и в этом потоке может произойти ошибка.

Не использование исключений упоминалось в некоторых ответах здесь в связи с играми https://gamedev.stackexchange.com/questions/103285/how-industy-games-handle-their-code-errors-and-exceptions

Использование исключений обсуждалось здесь: http://www.quora.com/Why-do-some-people-recommend-not-using-exception-handling-in-C++

Есть еще несколько источников, которые я читал, но это были самые последние, которые я просмотрел.

Личный Вывод:

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

5 ответов:

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

int main(int argc, const char **argv)
{
    try {
         // do stuff
         return EXIT_SUCCESS;
    } catch (...) {
        std::cerr << "Error: unknown exception" << std::endl;
        return EXIT_FAILURE;
    }
}
Тогда вы не сильно ошибетесь. Вы можете (и должны) добавлять специальные операторы catch для лучшего представления отчетов об ошибках.

Исключения при многопоточности

Существует два основных способа асинхронного выполнения кода в C++11 с использованием стандартных библиотечных функций: std::async и std::thread.

Сначала простой. std::async вернет std::future который будет захватывать и хранить любые необработанные исключения, брошенные в данной функции. Вызов std::future::get в будущем приведет к распространению любых исключений в вызывающий поток.

auto fut = std::async(std::launch::async, [] () { throw std::runtime_error {"oh dear"}; });
fut.get(); // fine, throws exception

С другой стороны, если исключение в объекте std::thread не обнаружено, то std::terminate будет называться:

try {
    std::thread t {[] () { throw std::runtime_error {"oh dear"};}};
    t.join();
} catch(...) {
    // only get here if std::thread constructor throws
}

Одним из решений этого может быть передача std::exception_ptr в объект std::thread, которому он может передать исключение:

void foo(std::exception_ptr& eptr)
{
    try {
        throw std::runtime_error {"oh dear"};
    } catch (...) {
        eptr = std::current_exception();
    }
}

void bar()
{
    std::exception_ptr eptr {};

    std::thread t {foo, std::ref(eptr)};

    try {
        // do stuff
    } catch(...) {
        t.join(); // t may also have thrown
        throw;
    }
    t.join();

    if (eptr) {
        std::rethrow_exception(eptr);
    }
}

Хотя лучше использовать std::package_task:

void foo()
{
    throw std::runtime_error {"oh dear"};
}

void bar()
{
    std::packaged_task<void()> task {foo};
    auto fut = task.get_future();

    std::thread t {std::move(task)};
    t.join();

    auto result = fut.get(); // throws here
}

Но если только у вас есть веские основания использовать std::thread, предпочесть std::async.

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

Вызов exit() не будет разматывать стек, откуда бы вы его ни вызвали. Если вы хотите выйти чисто, то что ты уже делаешь это идеально.

Вы уже приняли ответ, но я хотел бы добавить кое-что об этом:

Могу ли я просто явно вызвать что-то вроде exit() вместо этого?

Вы можете вызвать exit, но (вероятно) не должны.

std::exit следует зарезервировать для ситуаций, когда вы хотите выразить "выход прямо сейчас!", а не просто "приложению больше нечего делать".

Например, если вы должны были написать контроллер для лазера, используемого в лечении рака, ваш первый приоритет в случае, если что - то пошло не так, чтобы выключить лазер и вызвать std::exit - или, возможно, std::terminate (чтобы гарантировать, что любые побочные эффекты зависания, замедления или сбоя приложения не убьют пациента).

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

Является ли использование исключений правильным способом завершить работу моей программы, если все, что я хочу, это вывести сообщение об ошибке и закрыть (учитывая, что я могу быть глубоко в программе)?

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

Есть аргументы за / против исключений и кодов ошибок, и это хорошее чтение:

Исключения или коды ошибок

Могу ли я просто явно вызвать что-то вроде exit() вместо этого?

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

Еще один аналогичный вопрос:

Правильное использование exit () в c++?

Вы тоже есть недостаток в вашем подходе, так как вы не обрабатываете все исключения (C++). Вы хотите что-то вроде этого:

int main()
{
    Game game;
    try
    {
        game.run();
    }
    catch (BadResolutionException & e)
    {
        Notification::showErrorMessage(e.what(), "ERROR: Resolution");
        return 1;
    }
    catch (BadAssetException & e)
    {
        Notification::showErrorMessage(e.what(), "ERROR: Assets");
        return 1;
    }
    catch (std::bad_alloc & e)
    {
        Notification::showErrorMessage(e.what(), "ERROR: Memory");
        return 1;
    }
    catch (...)
    {
        // overload?
        Notification::showErrorMessage("ERROR: Unhandled");
        return 1;
    }

    return 0;
}

Если вы не обрабатываете все * исключения, вы можете прекратить игру, не сообщив вам ничего полезного.

Вы не можете обрабатывать все исключения. Смотрите эту ссылку:

C++ ловит все исключения

Из документации:

[[noreturn]] void exit (int status); Завершить процесс Завершает процесс нормально, выполняя регулярную очистку для завершения программ.

Обычное завершение программы выполняется следующим образом (в том же порядке): Объекты, связанные с текущим потоком с длительностью хранения потока, уничтожаются (только C++11). Объекты со статической продолжительностью хранения разрушен (C++) и функции, зарегистрированные с помощью atexit являются называемый. Все потоки C (открытые с помощью функций in ) закрываются (и сбрасываются, если они буферизованы), а все файлы, созданные с помощью tmpfile, удаляются. Управление возвращается в среду хоста.

Обратите внимание, что объекты с автоматическим хранением не уничтожаются вызовом exit (C++).

Если состояние равно нулю или EXIT_SUCCESS, то в среду хоста возвращается состояние успешного завершения. Если состояние равно EXIT_FAILURE, то хосту возвращается состояние неудачного завершения окружающая среда. В противном случае возвращаемый статус зависит от реализации системы и библиотеки.

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