Motivation
Традиционно при использовании исключений для сообщения о сбоях сайт бросок:
- создает объект исключения соответствующего типа, и
- набивает его данными, относящимися к обнаруженной ошибке.
Более высокий контекст в программе содержит уловку, которая:
- выбирает неудачи на основе типов исключений;
- проверяет объекты исключений на наличие данных, необходимых для решения проблемы.
Основная проблема с этим «традиционным» подходом заключается в том, что часто данные, доступные в точке броска, недостаточны для того, чтобы сайт улова справился с неисправностью.
Вот пример заявления об улове:
catch( file_read_error & e )
{
std::cerr << e.file_name();
}
А вот возможный совпадающий бросок:
void
read_file( FILE * f )
{
....
size_t nr=fread(buf,1,count,f);
if( ferror(f) )
throw file_read_error(???);
....
}
Очевидно, проблема в том, что обработчику требуется имя файла, но функция read_file не имеет имени файла, чтобы поместить его в объект исключения; все, что у него есть, это указатель FILE!
В попытке решить эту проблему мы можем изменить read_file, чтобы принять имя файла:
void
read_file( FILE * f, char const * name )
{
....
size_t nr=fread(buf,1,count,f);
if( ferror(f) )
throw file_read_error(name);
....
}
Это не реальное решение: оно просто перекладывает бремя предоставления имени файла на непосредственного абонента функции read_file.
В целом, данные, необходимые для обработки данного исключения, излучаемого библиотекой, зависят от программы, которая ссылается на нее. Многие контексты между броском и уловом могут иметь соответствующую информацию, которая должна быть передана обработчику исключения.
Exception wrapping
Идея обертывания исключений состоит в том, чтобы поймать исключение из функции более низкого уровня (например, функцию read_file выше) и бросить новый объект исключения, который содержит первоначальное исключение (и также несет имя файла). Этот метод, по-видимому, особенно популярен у программистов C++ с фоном Java.
Исключение обертывания приводит к следующим проблемам:
- Чтобы обернуть объект исключения, его необходимо скопировать, что может привести к нарезке.
- Обертывание практически невозможно использовать в общих контекстах.
Второй момент — это фактически особый случай нарушения принципа исключительного нейтралитета. Большинство контекстов в программе не могут обрабатывать исключения; такие контексты не должны мешать процессу обработки исключений.
The boost::exception solution
- Просто выведите свои типы исключений из ускорения:исключение.
- Уверенно ограничьте сайт бросок, чтобы предоставить только те данные, которые доступны естественным образом.
- Используйте нейтральные контексты между броском и уловом, чтобы увеличить исключения с более релевантными данными по мере их роста.
Например, в приведенном ниже заявлении о выбросе мы добавляем только код ошибки, поскольку это единственная информация, имеющая отношение к отказу, доступная в этом контексте:
struct exception_base: virtual std::exception, virtual boost::exception { };
struct io_error: virtual exception_base { };
struct file_read_error: virtual io_error { };
typedef boost::error_info<struct tag_errno_code,int> errno_code;
void
read_file( FILE * f )
{
....
size_t nr=fread(buf,1,count,f);
if( ferror(f) )
throw file_read_error() << errno_code(errno);
....
}
В более высоком нейтральном контексте мы добавляем имя файла к исключению any, которое происходит от увеличения:: Исключение:
typedef boost::error_info<struct tag_file_name,std::string> file_name;
....
try
{
if( FILE * fp=fopen("foo.txt","rt") )
{
shared_ptr<FILE> f(fp,fclose);
....
read_file(fp); //throws types deriving from boost::exception
do_something();
....
}
else
throw file_open_error() << errno_code(errno);
}
catch( boost::exception & e )
{
e << file_name("foo.txt");
throw;
}
Наконец, вот как обработчик извлекает данные из исключений, которые вытекают из повышения:: исключение :
catch( io_error & e )
{
std::cerr << "I/O Error!\n";
if( std::string const * fn=get_error_info<file_name>(e) )
std::cerr << "File name: " << *fn << "\n";
if( int const * c=get_error_info<errno_code>(e) )
std::cerr << "OS says: " << strerror(*c) << "\n";
}
Кроме того, boost::diagnostic_information можно использовать для составления автоматического (если не удобного для пользователя) сообщения, содержащего все объекты error_info, добавленные к boost:: Исключение. Это полезно для включения в журналы и другие диагностические объекты.