Какая потребность в специальных классах исключений?

Почему стандарт С++ начал изобретать классы std::exception? Какая их польза? Моей причиной для этого является следующее:

try
{
 throw std::string("boom");
}
catch (std::string str)
{
 std::cout << str << std::endl;
}

Прекрасно работает. Позже, если мне нужно, я могу просто сделать свои собственные легкие "исключения". Так почему я должен беспокоиться о std::exception?

6 ответов

Почему стандарт С++ придумывал классы std::exception? Какая их польза?

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

Обратите внимание, что стандартная библиотека api может вызывать множество различных видов исключений. Чтобы привести несколько примеров:

  • std::bad_alloc
  • std::bad_cast
  • std::bad_exception
  • std::bad_typeid
  • std::logic_error
  • std::runtime_error
  • std::bad_weak_ptr | C++11
  • std::bad_function_call | C++11
  • std::ios_base::failure | C++11
  • std::bad_variant_access | C++17

и т.д. std::exception - базовый класс для всех этих исключений:

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

Если мне нужно, я могу просто сделать свои легкие "исключения". Так почему я должен беспокоиться о std::exception?

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

  • может ловить стандартные библиотечные исключения, а также
  • исключения типа вашего настраиваемого класса исключений

Предоставление изображения: http://en.cppreference.com/w/cpp/error/exception


Почему стандарт С++ придумывал классы std:: exception? Какая их польза?

Наличие разных видов исключений позволяет обнаруживать определенные типы ошибок. Вывод исключений из общей базы допускает детализацию при поиске более общих или конкретных ошибок в зависимости от обстоятельств.

В С++ уже существует существующая система типа, поэтому стандартизация строк ошибок не нужна, если вы можете явно создать исключение желаемого типа на языке.

std:: exception и его производные классы существуют по двум основным причинам:

  • Стандартная библиотека должна иметь некоторую иерархию исключений для бросать в исключительных обстоятельствах. Было бы неуместным всегда бросайте std::string, потому что у вас не будет чистого пути целевые конкретные виды ошибок.

  • Предоставлять расширяемый интерфейс на основе классов для поставщиков библиотек, чтобы генерировать самые основные типы ошибок и обеспечивать общий резерв для пользователей. Возможно, вы захотите предоставить больше метаданных ошибок, чем простая строка what(), чтобы человек, поймавший вашу ошибку, мог более разумно восстановить ее.

    В то же время std:: exception в качестве общей базы допускает, что общий ресурс менее всеобъемлющий, чем ..., если пользователь заботится только об этом сообщении об ошибке.

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

Позже, если мне нужно, я могу просто сделать свои легкие "исключения". Так почему я должен беспокоиться о std:: exception?

Если вы наследуете от std:: runtime_error и используете свой собственный тип ошибки, вы можете ретроактивно добавлять метаданные ошибок, не переписывая блоки catch! В отличие от этого, если вы когда-либо изменили дизайн обработки ошибок, вам придется переписать все ваши std::string, потому что вы не можете надежно наследовать от std::string. Это не перспективное дизайнерское решение.

Если это сейчас не так уж плохо, представьте, чтобы ваш код стал общим для нескольких проектов как разделяемая библиотека с различными программистами, работающими над ним. Миграция на новую версию вашей библиотеки стала бы болью.

Это даже не упоминание о том, что std::string может генерировать собственные исключения во время копирования, конструирования или доступа к символам!

Веб-сайт Boost содержит некоторые хорошие рекомендации по обработке исключений и построению классов здесь.

История пользователя

Я пишу код сети и использую сторонний поставщик библиотека. При неверном ip-адресе, введенном пользователем, это библиотека выбрасывает пользовательское исключение nw:: invalid_ip, полученное из станд:: runtime_error. nw:: invalid_ip содержит описание what()сообщение об ошибке, но также и адрес correct_ip().

Я также использую std::vector для хранения сокетов, и я использую проверил на() вызов безопасного доступа к индексам. Я знаю, что если я вызовите at() по значению вне границ std:: out_of_range.

Я знаю, что другие вещи могут быть брошены также, но я не знаю, как обрабатывать их или что именно они могут быть.

Когда я получаю ошибку nw:: invalid_ip, я выхожу модальный с вводом для пользователя, заполненного недействительным IP-адресом, чтобы они могли редактировать и повторите попытку.

Для проблем std:: out_of_range я отвечаю, выполняя проверку целостности на сокетах и ​​фиксации отношения вектора/сокета, которое упало из-за синхронизации.

Для любых других проблем std:: exception я завершаю программу с помощью журнал ошибок. Наконец, у меня есть catch (...), который регистрирует "неизвестную ошибку!". а также завершается.

Было бы сложно сделать это с помощью только std::string.

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

ExampleExceptions.cpp

#include <vector>
#include <iostream>
#include <functional>
#include <stdexcept>
#include <bitset>
#include <string>
struct Base1 {
 virtual ~Base1(){}
};
struct Base2 {
 virtual ~Base2(){}
};
class Class1 : public Base1 {};
class Class2 : public Base2 {};
class CustomException : public std::runtime_error {
public:
 explicit CustomException(const std::string& what_arg, int errorCode):
 std::runtime_error(what_arg),
 errorCode(errorCode){
 }
 int whatErrorCode() const {
 return errorCode;
 }
private:
 int errorCode;
};
void tryWrap(typename std::function<void()> f){
 try {
 f();
 } catch(CustomException &e) {
 std::cout << "Custom Exception: " << e.what() << " Error Code: " << e.whatErrorCode() << std::endl;
 } catch(std::out_of_range &e) {
 std::cout << "Range exception: " << e.what() << std::endl;
 } catch(std::bad_cast &e) {
 std::cout << "Cast exception: " << e.what() << std::endl;
 } catch(std::exception &e) {
 std::cout << "General exception: " << e.what() << std::endl;
 } catch(...) {
 std::cout << "What just happened?" << std::endl;
 }
}
int main(){
 Class1 a;
 Class2 b;
 std::vector<class2> values;
 tryWrap([](){
 throw CustomException("My exception with an additional error code!", 42);
 });
 tryWrap([&](){
 values.at(10);
 });
 tryWrap([&](){
 Class2 c = dynamic_cast<class2&>(a);
 });
 tryWrap([&](){
 values.push_back(dynamic_cast<class2&>(a));
 values.at(1);
 });
 tryWrap([](){
 std::bitset<5> mybitset (std::string("01234"));
 });
 tryWrap([](){
 throw 5;
 });
}
</class2&></class2&></class2></void()></string></bitset></stdexcept></functional></iostream></vector>

Вывод:

Custom Exception: My exception with an additional error code! Error Code: 42
Range exception: vector::_M_range_check
Cast exception: std::bad_cast
Cast exception: std::bad_cast
General exception: bitset::_M_copy_from_ptr
What just happened?


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

Например, сегодня вы бросаете string в свой собственный код. Завтра вы решите добавить больше свойств для определенных случаев, таких как исключение подключения к базе данных. Вы можете просто извлечь из string, чтобы сделать это изменение; вам нужно будет написать новый класс исключений и изменить все обработчики исключений для string. Использование exception - это способ для обработчиков исключений использовать только те данные, которые им нужны, выбирать и выбирать исключения, поскольку они должны обрабатывать их.

Также, если вы выбрали и обработали только теги string -typed, вы пропустите все исключения, выброшенные из любого кода, который не является вашим. Если это различие является преднамеренным, лучше было бы его описать, используя общий класс исключений, чтобы обозначить это, а не общий тип string.

exception также более специфичен, чем string. Это означает, что разработчики библиотек могут писать функции, которые принимают исключения как параметры, которые яснее принятия string.

Все это по существу бесплатно, просто используйте exception вместо string.


Просто потому, что что-то "отлично работает" в примере с 6 линейными игрушками не означает, что он масштабируемый или поддерживаемый в реальном коде.

Рассмотрим эту функцию:

template<typename t="">
std::string convert(const T& t)
{
 return boost:lexical_cast<std::string>(t);
}
</std::string></typename>

Это может вызвать bad_alloc, если память для строки не может быть выделена или может быть сброшена bad_cast, если преобразование завершается неудачно.

Абонент этой функции может захотеть обработать неудавшийся случай преобразования, который указывает на плохой ввод, но не является фатальной ошибкой, но не хочет обрабатывать случай с отсутствием памяти, потому что они ничего не могут с этим поделать поэтому пусть исключение распространяется на стек. Это очень легко сделать в С++, например:

std::string s;
try {
 s = convert(val);
} catch (const std::bad_cast& e) {
 s = "failed";
}

Если исключения были просто выбраны как std::string, код должен быть:

std::string s;
try {
 s = convert(val);
} catch (const std::string& e) {
 if (e.find("bad_cast") != std::string::npos)
 s = "failed";
 else
 throw;
}

Это требует большего количества кода для реализации и зависит от точной формулировки строки исключения, которая может зависеть от реализации компилятора и определения boost::lexical_cast. Если каждая часть обработки исключений в системе должна была выполнять строковые сравнения, чтобы решить, может ли ошибка обрабатываться в этот момент, она будет беспорядочной и недопустимой. Небольшое изменение для написания сообщения об исключении в одной части системы, которая генерирует исключения, может привести к тому, что код обработки исключений в другой части системы перестанет работать. Это создает плотную связь между местоположением ошибки и каждым битом кода обработки ошибок в системе. Одно из преимуществ использования исключений заключается в том, чтобы позволить обработку ошибок быть отделенными от основной логики, вы теряете это преимущество, если создаете зависимости, основанные на сравнении строк во всей системе.

Обработка исключений на С++ ловит вещи путем сопоставления по типу исключения, она не улавливается путем сопоставления по значению исключения, поэтому имеет смысл бросать вещи разных типов, чтобы обеспечить мелкозернистую обработку. Бросать вещи одного типа строк и обрабатывать их на основе строкового значения является грязным, не переносным и более сложным.

Позже, если понадобится, я могу просто сделать свои собственные легкие "исключения". Так почему я должен беспокоиться о std:: exception?

Если ваш код полезен и многократно используется, и я хочу использовать его в составе моей системы, должен ли я добавлять обработку исключений, которая ловит все ваши легкие типы? Почему вся моя система должна заботиться о внутренних деталях библиотеки, на которую опирается один бит системы? Если ваши настраиваемые типы исключений получены из std::exception, я могу поймать их const std::exception&, не зная (или не заботясь) о конкретных типах.


Ничего себе, я удивлен, что никто не упомянул об этом:

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

  • У них должен быть общий базовый класс, позволяющий вам иметь средства для отображения правильного сообщения пользователю без необходимости знать все типы исключений, которые могла бы выполнить ваша программа (что невозможно при использовании внешние закрытые исходные библиотеки).


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

Но если ваш класс будет использоваться другими (программистами), как они будут обрабатывать исключение.

Если ваш класс throws a string, который описывает ошибку, это не помогло бы, так как потребители вашего класса предпочли бы более стандартный способ (catch объект исключения и запросить его, какой метод) обрабатывать exception, а не ловить строку.

Также вы можете поймать исключения, которые бросает стандартная библиотека, поймав объект exception

Вы можете переопределить метод what класса exception, чтобы предоставить дополнительную информацию об ошибке.

licensed under cc by-sa 3.0 with attribution.