Стандартный способ в С++ для определения класса исключения и исключения исключений

Я хочу создать класс с функциями, которые могут генерировать исключения, которые я хочу поймать, когда я его использую. Я унаследовал my_exception от стандартного класса исключения. Я реализую функцию what() так, чтобы она возвращала строку, которая хранится в частной строковой переменной

Я думаю, было бы лучше определить исключение как вложенный класс, как это было сделано в iostream-библиотеке с ios_base:: failure.

Что я менее уверен, где и как я должен определить объект my_excpetion. Мне хотелось бы видеть внутренний код функций iostream и посмотреть, как они это сделали. Я подумал о нескольких вариантах:

  • Для каждой причины исключения я могу определить статический экземпляр my_exception, с конструктором, который получает строку и сохраняет ее в своем личном указателе строки.

  • По каждой причине исключения я могу определить другой класс, который наследует от my_exception и реализует то, что в качестве функции возвращает постоянную строку (причина). Я могу хранить экземпляр каждого из этих подклассов исключений или бросать тип. BTW, когда мы обычно бросаем тип, а не экземпляр?

  • Я предполагаю, что это неправильно: каждый раз, когда я хочу создать исключение, создайте новое my_exception с конструктором, который получает строку. Это делается на Java, но, как я понимаю, это будет проблематично в С++, потому что исключение должно быть удалено где-то. Правильно?

Я думаю, что первый - правильный, не так ли? Есть ли более стандартные варианты?

Большое спасибо!

Ответ 1

Короткий ответ: вы захотите исключить исключения как объекты, а не как указатели. Вы поймаете их как ссылки.

Более длинный ответ: все доступные вам параметры. В общем, причина, по которой вы захотите бросить объект, а не указатель, заключается в том, что вы выбираете себя и своих клиентов, когда исключение поймано.

Если вы поймаете указатель, catch (my_exception* e), то вы не знаете, глядя на него, следует ли вам удалить память или нет.

Если вы поймаете значение, catch (my_exception e), тогда у вас есть риск нарезки, если объект исключения окажется базовым классом с некоторыми другими производными классами.

Ловля ссылками не имеет ни одной из этих проблем. Если вы пишете catch (my_exception& r), вы можете поймать полиморфные объекты, и вам не нужно беспокоиться о том, чтобы освободить память.

Итак, чтобы ответить на ваш другой вопрос, когда вы бросаете, просто бросайте временный объект: throw my_exception(). Это создает временный объект, который (вероятно) скопирован при броске, пойман по ссылке и автоматически уничтожается, когда он выходит из области видимости в конце вашего блока catch. (Это на самом деле другое преимущество catch-by-reference над catch-by-value, так как catch-by-value создает еще одну копию, когда она попадает.)

Что касается других, производных классов исключений, это выбор стиля. Вывод из my_exception с другой реализацией() является довольно стандартным. Я бы не сказал, что вам нужно увлекаться хранением строк или экземпляров в статических объектах - они небольшие, и для их построения требуется практически не время, чем процесс разворачивания стека при вызове исключения.

Ответ 2

Если вы выходите из std:: runtime_error, вам не нужно определять свой собственный элемент для хранения строки. Это делается для вас в std:: exception (базе std:: runtime_error). Не определено, как исключение хранит строку, но она всегда должна работать.

#include <stdexcept>
#include <string>

struct MyException: public std::runtime_error
{
    MyException(std::string const& message)
        : std::runtime_error(message + " Was thrown")
    {}
};

Ответ 3

Ничего плохого в ваших вариантах. Номер 3 в порядке, если вы создаете локальную переменную и не используете new, потому что нет необходимости удалять объект исключения - он будет уничтожен, как только вы выбросите. Вам нужно будет создать конструктор копирования и оператор копирования, потому что брошенное исключение на самом деле будет копией того, которое вы передаете оператору throw.

Вариант 1 был бы необычным, потому что он обычно не нужен.

Для варианта 2 вы должны создать экземпляр класса для броска. Невозможно бросить тип, только экземпляр типа.