|
|
|||||||||||||||||||||||||||||
|
Что нового в работе с исключениями в C++11Источник: habrahabr prograholic
В интернете довольно много говорят о новых возможностях C++11: auto, lambda, variadic templates. Но как-то обошли стороной новые возможности работы с исключениями, которые предоставляет язык и стандартная библиотека. От предыдущей версии стандарта остался механизм генерации исключений (throw), проверка того, что мы находимся в процессе обработки исключения (std::uncaught_exception), механизм остановки, если исключение не было обработано. Также есть иерархия стандартных исключений на базе класса std::exception. Новый стандарт добавляет к этим вещам еще несколько сущностей, которые, на мой взгляд, могут существенно упростить работу с исключениями в C++.
exception_ptrИтак, самое первое, с чем мы можем столкнуться - это std::exception_ptr. Этот тип позволяет в себе хранить исключение абсолютно любого типа. Стандарт не оговаривает каким образом получен этот тип. Это может быть typedef, это может быть реализация класса. Его поведение сходно поведению std::shared_ptr, то есть его можно копировать, передавать в качестве параметра, при этом само исключение не копируется. Основное предназначение exception_ptr - это передача исключений в качестве параметров функции, возможна передача исключений между потоками. Таким образом, объекты данного типа позволяют сделать обработку ошибок более гибкой:
Если мы запустим функцию exceptionPtrSample на выполнение, то увидим примерно следующий результат: throwing 10… Для того чтобы можно было удобно работать с exception_ptr, есть несколько вспомогательных функций:
Передача исключений между потокамиКак мне кажется, тип exception_ptr создавался именно для решения проблемы передачи исключений между потоками, поэтому давайте разберемся, каким образом мы можем передать исключение из одного потока в другой:
Вообще, многопоточность в C++11 - это обширная тема, там есть свои тонкости, нюансы и о них следует писать отдельно. Сейчас мы рассмотрим этот пример только ради передачи исключения. Запускаем функцию worker в отдельном потоке, и эта функция кидает исключение. Объект класса promise позволяет организовать связь между разными потоками, и атомарно передать значения из одного потока в другой (или исключение). В данном примере мы как раз пользуемся методом set_exception, который принимает exception_ptr в качестве параметра. Для того чтобы получить значение, мы создаем объект класса future- это наш result и вызываем метод get. Также необходимо у потока вызвать метод detach или join, так как при разрушении объекта t в деструкторе проверяется, чтобы joinable() == false, иначе вызывается std::terminate. Скорее всего, это связано с тем, чтобы программист не "отпускал потоки на волю", а всегда следил за ними (либо отпускал явно с помощью метода detach) Отдельно стоит сказать об использовании многопоточности в gcc-4.7. Изначально этот пример у меня не работал (выбрасывал исключение), погуглив, я выяснил, что для использования std::thread необходимо передать линкеру флаг-pthread. Так как я использую CMake в качестве системы сборки, то эта задача упрощается (тут сложность может возникнуть при использовании gcc на разных платформах, например на sparc solaris используется флаг -thread вместо -pthread) - есть специальный CMake модуль Threads, в котором эта проблема решена: find_package(Threads REQUIRED)
Nested exceptionsКак видно из названия, данный механизм позволяет "прицепить" к кидаемому исключению другие исключения (которые могли быть брошены раньше). Например, если у нас есть своя иерархия исключений, то мы можем ловить все "сторонние" исключения, прицеплять их к своим исключениям, а при обработке своих исключений мы можем выводить доп. информацию, которая к ним "прицеплена" (например, при отладке, мы можем печатать информацию о сторонних исключениях). Хороший пример использования nested exception приведен на cppreference.com, мой пример, отчасти пересекается с ним:
Итак, у нас есть сторонняя функция, которая кидает исключение, мы можем написать адаптер, который ловит "сторонние" исключения, и из них делает "наше" исключение: в качестве "сторонней" функции и "нашей" функции выступают соответственно third_party_worker и ourWorker. Мы ловим все исключения, и бросаем далее уже наше (our_exception) исключение, при этом, к нему было прицеплено какое-то (мы в принципе можем и не знать какое) "стороннее" исключение. После этого работаем уже с нашими исключениями. При этом, если нам понадобится более детальная информация о том, что происходило на "нижнем" уровне, то мы всегда можем вызвать функциюrethrow_if_nested. Данная функция анализирует, если ли прицепленное (вложенное) исключение, и если есть, то бросает это вложенное исключение. Функция exceptionHandler принимает "наше" исключение и дополнительный флаг, который разрешает или запрещает вывод информации о стороннем исключении. Мы можем управлять выводом сторонних исключений, управляя параметром catchNested, например, из файла конфигурации (или в зависимости от сборки - Release, Debug). Для работы с nested exception есть один класс и две функции:
В принципе, механизм nested exception довольно интересный, хотя реализация может быть довольно тривиальна, ее можно сделать самому, с помощью ранее описанных функций current_exception и rethrow_exception. В той же реализации gcc, класс nested_exception содержит одно поле типаexception_ptr, которое инициализируется в конструкторе с помощью функцииcurrent_exception, а реализация метода rethrow_nested просто вызывает функцию rethrow_exception.
Спецификация noexceptДанный механизм можно рассматривать как расширенный (и ныне устаревший) механизм throw(). Основное его предназначение как и раньше - гарантия того что, функция не бросит исключение (если гарантия нарушается, то вызывается std::terminate). Используется этот механизм в двух видах, аналогично старому throw()
И в новом виде:
При этом, если значение выражения вычислено как истина, то функция помечается как noexcept, иначе, такой гарантии нет. Также есть соответствующий оператор noexcept(expression), который тоже выполняется в compile time, этот оператор возвращает истину, если выражение не бросает исключение:
Данный код для gcc-4.7.2 выводит: noexcept int(): 1 Здесь мы видим, что конструктор встроенного типа int не бросает исключение, а конструктор вектора может бросить (не помечен как noexcept).
Данный пример выводит: noexcept int(): 1
РезюмеСтандарт C++11 привнес много нового в обработку ошибок, конечно, ключевой особенностью здесь остается exception_ptr и возможность передачи произвольных исключений как обычные объекты (в функции, передавать исключения между потоками). Раньше, в каждом потоке приходилось писать развесистый try… catch для всех исключений, а этот функционал существенно минимизирует количество try… catch кода. Также появилась возможность создавать вложенные исключения, в принципе в библиотеке boost есть механизм boost::exception, который позволяет прикреплять к объекту произвольные данные (например коды ошибок, свои сообщения и пр), но он решает другие задачи - передачу произвольных данных внутри исключения. Ну и наконец, для тех, кому нужны гарантии, что код не бросит исключения, есть noexcept, в частности, многие части стандартной библиотеки используют этот механизм. Ссылки по теме
|
|