Недавно потребовалось как-то упорядочить исключения в программе. Посмотрел в сторону boost::exception, благо boost в проекте уже используется.
Библиотечка позволяет решить две проблемы:
- Добавлять к исключению любого типа (производного от boost::exception) произвольную информацию, причем не только в месте выброса, но и на более высоких промежуточных уровнях обработки
- Клонировать текущее исключение, сохранять его и выкидывать в другом месте, а также “вкладывать” в другое исключение.
Пока остановимся на первом пункте, о втором я расскажу в очередном посте.
Сохранение в исключении произвольной информации
Выглядит примерно так:
1 typedef boost::error_info<struct errinfo_str_, std::string> errinfo_str; // 1
2 typedef boost::error_info<struct errinfo_file_, Handle> errinfo_file;
3
4 ...
5 try
6 {
7 try
8 {
9 throw MyException() << errinfo_file(hfile); // 2
10 }
11 catch(MyException &e)
12 {
13 e << errinfo_str("bla-bla"); // 3
14 throw;
15 }
16 }
17 catch(const boost::esception &e)
18 {
19 std::cout << diagnostic_information(e); // 4
20 if(std::string const * inf=get_error_info<errinfo_str>(e) ) //5
21 std::cout << "Info: " << *inf << "\n";
22 }
23
24
Основная идея – мы можем поэтапно добавлять к исключению информацию. В момент выкидывания исключения часть информации может быть недоступна; она может быть добавлена на верхних уровнях и т.д.
В строке //1 определяется описание пользовательской информации, которую можно сохранить в исключении. Первый аргумент error_info – это уникальный тип, второй – тип данных, который должен храниться в исключении (кстати, вы знали, что можно объявить структуру внутри шаблона? Я – нет).
В строке //2 выкидывается исключение, в котором сохраняется информация о файле.
В строке //3 к перехваченному исключению добавляется новая информация.
В //4 используется функция boost::diagnostic_information, которая формирует строку с описанием исключения и всей присоединенной к нему информации. Строка не особо человеко-читаема и пользователю ее лучше не видеть. Может быть полезно например для сохранения информации в лог.
В //5 происходит проверка на наличии в исключении данных errinfo_str и если они есть, выдается соответствующая строка.
Иерархия исключений
С учетом такой техники классы исключений становятся простыми "маркерами", не содержащими данных или методов. Каждый пользовательский класс исключений наследуется от boost::exception и возможно от std::exception. Чтобы пользовательские исключения могли использовать множественное наследование друг от друга, авторы библиотеки рекомендуют виртуальное наследование.
Удобно также завести свой собственный базовый класс исключений.
Получается примерно вот так:
1 struct MyException : virtual boost::exception, virtual std::exception
2 {
3 const char* what()const throw() { return boost::diagnostic_information_what(*this); }
4 };
5
6 struct MyFileException : virtual MyException {};
7 struct MyNetException : virtual MyException {};
8 struct MyComplexException : virtual MyFileException, virtual MyNetException {};
Использование
В библиотеке определен макрос BOOST_THROW_EXCEPTION, который выкидывает указанное исключение, добавляя к нему имя функции, имя и строку файла. На самом деле он не выкидывает исключение, а вызывает boost::throw_exception. Поведение последней настраивается опциями компиляции, по умолчанию она оборачивает исключение для возможности клонирования.
Возможно для проекта имеет смысл завести свой собственный макрос, который будет добавлять что-либо еще (к примеру стек-трейс).
Впечатления
Библиотечка удобна в использовании, относительно невелика и понятно написана.
Однако многие возможности скрыты в недокументированном внутреннем пространстве имен. Например, нельзя перебрать все сохраненные в исключении данные (как это делает diagnostic_information).
Предполагается что остальные части буста используют эту библиотеку для своих исключений, а так как функцию boost::throw_exception можно написать самому, то можно например встроить во все исключения стек-трейс. Однако к сожалению тот же boost::filesystem использует свой собственный макрос, который раскрывается в простой throw. В комментариях написано, что они отказались от boost::exception, так как она дает много лишней информации.