На волне темы про использование исключений подсчитал, как часто в исходниках последней стабильной версии SO-5.5.18 встречаются блоки try-catch. Общий объем ядра SO-5 порядка 25KLOC (не считая пустых строк и комментариев). Всего насчитал 25 блоков try (в двух-трех блоках по 2 секции catch). Из общего числа catch-ей:
- в 10 случаях catch используется для преобразования одного типа исключения в другое. В нескольких из этих случаев перед пробросом нового исключения выполняются действия по очистке/откату операций;
- в 7 случаях в catch-е иницируется вызов std::abort() (перед этом зачастую идет логирование проблемы);
- в 4-х случаях исключение "проглатывается". В двух из них предварительно логируется;
- в 3-х случаях ловятся исключения, выпущенные из обработчиков событий агентов. В этих catch-ах происходит то, что описывалось в свежей статье на Хабре;
- в одном случае ловится исключение, выпущенное из обработчика события агента, для фиксации этого исключения в std::promise. Этот сценарий используется для синхронного взаимодействия агентов;
- в одном случае используется логика на исключениях: с помощью исключения разрывается цикл выборки событий из очереди одного из диспетчеров. Оказалось, что такая реализация цикла самая простая;
- и в одном случае catch используется для эмуляции noexcept для старых VC++ компиляторов.
В общем, получается где-то по одному try-catch на тысячу строк кода.
Отдельно можно сказать про случаи с проглатыванием исключений. В двух из них исключения логируются, но выбрасываются, т.к. исключение вылетает из нотификаторов регистрации и дерегистрации коопераций. Эти нотификаторы вызываются когда основная операция (т.е. либо регистрация, либо дерегистрация) уже завершена, была сделана попытка проинформировать пользователя об этом. Но не получилось. На основную операцию такой сбой повлиять не может, сделать что-то осмысленное с исключением уже нельзя, а прерывать все приложение через std::abort() в данном случае представляется слишком жестоким.
А вот два других случая с игнорированием исключений интереснее. Один из них такой (сорри за качество английского):
//! Switching storage from map to vector.
void
switch_storage_to_vector()
{
// All exceptions will be ignored.
// It is because an exception can be thrown on staged 1-2-3,
// but not on stage 4.
try
{
// Stage 1. New containers to swap values with old containers.
// An exception could be thrown in the constructors.
map_type empty_map;
vector_type new_storage;
// Stage 2. Preallocate necessary space in the new vector.
// An exception can be thrown here.
new_storage.reserve( m_map.size() );
// Stage 3. Copy items from old map to the new vector.
// We do not expect exceptions here at v.5.5.12, but
// the situation can change in the future versions.
// Use the fact that items in map is already ordered.
std::for_each( m_map.begin(), m_map.end(),
[&new_storage]( const map_type::value_type & info ) {
new_storage.push_back( info.second );
} );
// Stage 4. Swapping.
// No exceptions expected here.
m_vector.swap( new_storage );
m_map.swap( empty_map );
m_storage = storage_type::vector;
}
catch(...)
{}
}
|
Тут в транзакционном стиле делается попытка сменить внутреннее представление одной из структур данных. Если по ходу дела возникают исключения (прежде всего bad_alloc), то операция откатывается и не имеет эффекта. При этом исключение проглатывается, т.к. смысла прокидывать его наверх нет -- ничего же не изменилось.
Какова мораль? Да нет никакой морали. Просто любопытно стало. Думаю, что для библиотек небольшое количество try-catch -- это нормально. В прикладном кода плотность испрользования try-catch наверняка выше.