Исключения внутри исключений в С++

При работе с С# или Java я использовал для создания классов исключений, которые включали другие исключения в члены класса. Например:

public class MyException : Exception {
    private MyException ex;
    private String message;

    public String what() {
        return this.message;
    }

    public String stack() {
        if(ex != null) {
            StringBuilder s;
            s.append(this.what());
            s.append(this.ex.stack());
            return s.toString();
        }

        return this.what();
    }
}

Я искал примеры по одному и тому же вопросу, но для С++ и я не мог найти их (возможно, я не искал правильные условия, так как вы можете видеть, что заголовок этого вопроса не очень причудливый).

В любом случае, каков правильный способ сделать это на С++? Хранить ли внутреннее исключение как указатель или ссылку? (Я думаю, мне может понадобиться указатель, поэтому он может быть пустым, когда это первое исключение). Когда я создаю исключение, должен ли он быть указателем, созданным с помощью new?

EDIT: Возможно, то, что я написал, было немного запутанной или не известной (-принятой) практикой программирования. Поэтому я укажу, как я хотел использовать этот класс со сниппетом:

 try {
     // Code that throws an exception of type MyException
 } catch(MyException ex) {
     MyException another = new MyException();
     another.setEx(ex);
     another.setMessage("A message that explains where the other exception was caught and on what conditions");

     throw another;
 }

Ответ 1

Нет правильного способа сделать это со стандартными исключениями в С++ 03, потому что они предназначены для использования в полиморфном виде, но не могут быть клонированы. Поэтому, если вы поймаете std::exception const& e, вы можете сохранить копию, но это приведет к разрезанию, потеряв всю полезную информацию. Вы должны не хранить указатель или ссылку на исключение, потому что его срок службы подходит к концу, как только выйдет из предложения catch (при условии, что вы не восстановите исходное исключение).

Вы можете обойти это ограничение, если бы знали все типы, которые могут быть брошены, и тестировать их все, но это не красивый дизайн (т.е. он подрывает полиморфизм). Имеет смысл написать базовый класс исключений, который можно клонировать, и поймать его. У вас все еще есть проблема, если вы поймаете std::exception, который пришел от другого кода.

В этот момент я вынужден указать Boost.Exception. Это упрощает создание собственной иерархии исключений и предоставляет различные утилиты, среди которых boost::exception_ptr. Затем вы можете:

typedef boost::error_info<struct tag_nested_exception, boost::exception_ptr>
    nested_exception;

// ...
catch(...) {
    // better: use BOOST_THROW_EXCEPTION
    throw your_exception_type() << nested_exception( boost::current_exception() );
}

Это настолько полезно, что boost::diagnostic_info поддерживает его и отображает вложенное исключение для вас (оно недокументировано). Было даже высказано предположение, что этот nested_exception typedef также должен быть частью библиотеки; в то же время легко написать его самостоятельно.

Не ожидайте магии, хотя: boost::current_exception "захватывает" активное исключение (мелкая печать: или его клон) штраф только в том случае, если сайт броска использовал boost::enable_current_exception. (Функционально это моральный эквивалент использования базового класса исключений, который можно клонировать). Если это не так, это не сработает, но некоторая информация может быть потеряна.


Как последнее замечание, знайте, что дизайн исключения Boost.Exception был принят для С++ 0x. Таким образом, следующее правильно сохраняет активное исключение, причем ни одно из предупреждений boost::current_exception, поскольку оно имеет поддержку языка:

// you can still use Boost.Exception:
typedef boost::error_info<struct tag_nested_exception, std::exception_ptr>
    nested_exception;

// ...
catch(...) {
    // e has type std::exception_ptr
    auto e = std::current_exception();
    // internally store the std::exception_ptr
    throw your_exception_type(e);

    // or with Boost
    BOOST_THROW_EXCEPTION( your_exception_type() << nested_exception(e) );
}

Существует также тип std::nested_exception, который можно легко использовать следующим образом:

catch(...) {
    // throws an unspecified type derived from your_exception_type
    // and std::nested_exception
    std::throw_with_nested(your_exception_type());
}

Ответ 2

Я думаю, вы могли бы выбросить unique_ptr<some_exception>, а затем сделать inner_exception еще один unique_ptr, который станет владельцем.

Ответ 3

Я не думаю, что нам нужно свойство экземпляра класса (т.е. ex) в Java/С#/С++, если ваш класс не создан в одноэлементном шаблоне. Кроме того, здесь - это учебное пособие, которое вы можете захотеть посмотреть.