Как работает эта реализация цепочки исключений?
Ранее я задавал вопрос о том, как связать исключения в C++, и один из ответов дал изящное решение, как это можно сделать. Проблема в том, что я не понимаю код, и пытаться вести такого рода обсуждения в комментариях - слишком много хлопот. Поэтому я решил, что лучше начать новый вопрос полностью.
Код приведен ниже, и я четко обозначил каждый раздел, который не получил. Описание того, что я не понимаю, приведено ниже кода. Код был написан Potatoswatter.
Код
struct exception_data { // abstract base class; may contain anything
virtual ~exception_data() {}
};
struct chained_exception : std::exception {
chained_exception( std::string const &s, exception_data *d = NULL )
: data(d), descr(s) {
try {
link = new chained_exception;
// ----------------------------------------------------------------
// How does this work (section 1)?
throw;
// ----------------------------------------------------------------
} catch ( chained_exception &prev ) {
// ----------------------------------------------------------------
// How does this work (section 2)?
swap( *link, prev );
// ----------------------------------------------------------------
} // catch std::bad_alloc somehow...
}
friend void swap( chained_exception &lhs, chained_exception &rhs ) {
std::swap( lhs.link, rhs.link );
std::swap( lhs.data, rhs.data );
swap( lhs.descr, rhs.descr );
}
virtual char const *what() const throw() { return descr.c_str(); }
virtual ~chained_exception() throw() {
// --------------------------------------------------------------------
// How does this work (section 3)?
if ( link && link->link ) delete link; // do not delete terminator
// --------------------------------------------------------------------
delete data;
}
chained_exception *link; // always on heap
exception_data *data; // always on heap
std::string descr; // keeps data on heap
private:
chained_exception() : link(), data() {}
friend int main();
};
void f() {
try {
throw chained_exception( "humbug!" );
} catch ( std::exception & ) {
try {
throw chained_exception( "bah" );
} catch ( chained_exception &e ) {
chained_exception *ep = &e;
for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
// Print ep->what() to std::cerr
}
}
}
try {
throw chained_exception( "meh!" );
} catch ( chained_exception &e ) {
for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
// Print ep->what() to std::cerr
}
}
}
int main() try {
// ------------------------------------------------------------------------
// How does this work (section 4)?
throw chained_exception(); // create dummy end-of-chain
// ------------------------------------------------------------------------
} catch( chained_exception & ) {
// body of main goes here
f();
}
Выполнение кода дает следующий вывод:
bah
humbug!
meh!
Что я не понимаю
throw;
внутриtry
-блок: я никогда не видел этого раньше. Единственное место, где я думалthrow;
быть действительным был внутриcatch
-блок, чтобы отбросить то, что было поймано. Так, что это делает? Некоторая отладка, очевидно, показывает, что выброшенное исключение - это то, что было выброшено ранее, но это было внутри совершенно другогоtry
-блок. На самом деле, это было даже за пределамиstruct
Объявление!Обмен полей: зачем нам нужно менять поля исключений? Разве не достаточно просто скопировать указатели? Это чтобы предотвратить преждевременное удаление структур, на которые указывают поля, из кучи?
Проверьте
link
а такжеlink
ссылка: я могу понять, проверяя, чтоlink
не являетсяNULL
(хотя удалениеNULL
указатель не имеет никакого эффекта), но зачем проверятьlink
ссылка?Брось фиктивное исключение: зачем нужен этот манекен? Это брошено, но затем упало. Зачем нам это как конец цепи?
1 ответ
Умный код - спасибо этой воде. Я думаю, что мне придется найти способ обойти последний пункт, хотя.
throw;
сбрасывает активное исключение. Это действительно только еслиcatch
блок находится в стеке. Я не могу вспомнить, где я столкнулся с этим лакомым кусочком, но, вероятно, это было на SO в контексте какого-то другого вопроса. Голый бросок дает нам доступ к текущему исключению, перехватывая его вchained_exception
конструктор. Другими словами,prev
в конструкторе есть ссылка на исключение, которое мы сейчас обрабатываем.Вы правы здесь. Это предотвращает двойное удаление.
Стражное исключение, брошенное в
main
, никогда не должен быть удален. Одним из идентифицирующих атрибутов этого исключения является то, что этоlink
участникNULL
,Это та часть, которая мне не нравится, но я не могу придумать легкий путь. Единственное видимое
chained_exception
конструктор может быть вызван только когдаcatch
Блок активен. IIRC, голый бросок без активногоcatch
блок нет-нет. Итак, обходной путь заключается в том, чтобы добавитьmain
и поместите весь ваш код вcatch
блок.
Теперь, если вы попробуете этот метод в многопоточном коде, убедитесь, что вы хорошо понимаете (4). Вы должны будете повторить это в вашей точке входа потока.