Как работает эта реализация цепочки исключений? - PullRequest
3 голосов
/ 25 августа 2010

Ранее я задавал вопрос о том, как связывать исключения в 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 'ссылка s: Я могу понять, проверяя, что link не NULL (хотя удаление указателя NULL не имеет никакого эффекта), но зачем проверять ссылку link?

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

1 Ответ

4 голосов
/ 25 августа 2010

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...