Как работает эта реализация цепочки исключений?

Ранее я задавал вопрос о том, как связать исключения в 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!

Что я не понимаю

  1. throw; внутри try -блок: я никогда не видел этого раньше. Единственное место, где я думал throw; быть действительным был внутри catch -блок, чтобы отбросить то, что было поймано. Так, что это делает? Некоторая отладка, очевидно, показывает, что выброшенное исключение - это то, что было выброшено ранее, но это было внутри совершенно другого try -блок. На самом деле, это было даже за пределами struct Объявление!

  2. Обмен полей: зачем нам нужно менять поля исключений? Разве не достаточно просто скопировать указатели? Это чтобы предотвратить преждевременное удаление структур, на которые указывают поля, из кучи?

  3. Проверьте link а также link ссылка: я могу понять, проверяя, что link не является NULL (хотя удаление NULL указатель не имеет никакого эффекта), но зачем проверять link ссылка?

  4. Брось фиктивное исключение: зачем нужен этот манекен? Это брошено, но затем упало. Зачем нам это как конец цепи?

1 ответ

Решение

Умный код - спасибо этой воде. Я думаю, что мне придется найти способ обойти последний пункт, хотя.

  1. throw; сбрасывает активное исключение. Это действительно только если catch блок находится в стеке. Я не могу вспомнить, где я столкнулся с этим лакомым кусочком, но, вероятно, это было на SO в контексте какого-то другого вопроса. Голый бросок дает нам доступ к текущему исключению, перехватывая его в chained_exception конструктор. Другими словами, prev в конструкторе есть ссылка на исключение, которое мы сейчас обрабатываем.

  2. Вы правы здесь. Это предотвращает двойное удаление.

  3. Стражное исключение, брошенное в main, никогда не должен быть удален. Одним из идентифицирующих атрибутов этого исключения является то, что это link участник NULL,

  4. Это та часть, которая мне не нравится, но я не могу придумать легкий путь. Единственное видимое chained_exception конструктор может быть вызван только когда catch Блок активен. IIRC, голый бросок без активного catch блок нет-нет. Итак, обходной путь заключается в том, чтобы добавить main и поместите весь ваш код в catch блок.

Теперь, если вы попробуете этот метод в многопоточном коде, убедитесь, что вы хорошо понимаете (4). Вы должны будете повторить это в вашей точке входа потока.

Другие вопросы по тегам