18.1.5. Иерархии классов исключений

Классы исключений (см. раздел 5.6.3) стандартной библиотеки формируют иерархию наследования (см. главу 15), представленную на рис. 18.1.

Рис. 18.1. Иерархия классов исключений стандартной библиотеки

Единственными функциями, определенными типом exception, являются конструктор копий, оператор присвоения копий, виртуальный деструктор и виртуальная функция-член what(). Она возвращает указатель типа const char* на символьный массив с нулевым символом в конце и, как гарантируется, не передает никаких исключений.

Классы исключений exception, bad_cast и bad_alloc определяют также стандартный конструктор. Классы runtime_error и logic_error не имеют стандартного конструктора, но имеют конструкторы, получающие символьную строку в стиле С или аргумент библиотечного типа string. Эти аргументы предназначены для дополнительной информации об ошибке. Функция what() этих классов возвращает сообщение, использованное для инициализации объекта исключения. Поскольку функция what() виртуальная, при обработке ссылки на базовый тип вызов функции what() выполнит ту версию, которая соответствует динамическому типу объекта исключения.

Классы исключения для приложения книжного магазина

В приложениях иерархию исключений зачастую дополняют, определяя классы, производные от класса exception (или другого библиотечного класса, производного от него). Такие классы представляют исключения, специфические для данного приложения.

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

// гипотетический класс исключения для приложения книжного магазина

class out_of_stock: public std::runtime_error {

public:

 explicit out_of_stock(const std::string &s):

  std::runtime_error(s) { }

};

class isbn_mismatch: public std::logic_error {

public:

 explicit isbn_mismatch(const std::string &s):

  std::logic_error(s) { }

 isbn_mismatch(const std::string &s,

  const std::string &lhs, const std::string &rhs):

  std::logic_error(s), left(lhs), right(rhs) { }

 const std::string left, right;

};

Здесь специфические для приложения классы исключения определены как производные от стандартного класса исключения. Любую иерархию классов, включая иерархию исключений, можно рассматривать как слоистую структуру. По мере углубления иерархии каждый слой становится более специализированным. Например, первым и наиболее общим слоем иерархии является класс exception. При получении объекта этого типа будет известно только то, что в приложении произошла какая-то ошибка.

Второй слой специализирует исключение на две обширные категории: ошибки времени выполнения и логические ошибки. Ошибки времени выполнения могут быть обнаружены только при запуске программы. Логические ошибки, в принципе, могут быть обнаружены в приложении.

Классы исключений книжного магазина представляют даже более специализированный слой. Класс out_of_stock представляет проблему времени выполнения, специфическую для данного приложения. Он используется для оповещения о нарушении порядка выполнения. Класс исключения isbn_mismatch представляет собой более специализированную форму класса logic_error. В принципе программа может обнаружить несоответствие ISBN, вызвав функцию isbn().

Использование собственных типов исключений

Собственные классы исключений применяются точно так же, как и классы стандартной библиотеки. Одна часть программы передает объект одного из этих классов, а другая получает и обрабатывает его, устраняя проблему. Например, для перегруженного оператора суммы класса Sales_item можно создать класс исключения isbn_mismatch, передаваемого в случае обнаружения ошибки несовпадения ISBN.

// передает исключение, если isbn объектов не совпадают

Sales_data&

Sales_data::operator+=(const Sales_data& rhs) {

 if (isbn() != rhs.isbn())

  throw isbn_mismatch("wrong isbns", isbn(), rhs.isbn());

 units_sold += rhs.units_sold;

 revenue += rhs.revenue;

 return *this;

}

Обнаружив эту ошибку, использующий оператор += код сможет передать соответствующее сообщение об ошибке и продолжить работу.

// применение исключения в приложении книжного магазина

Sales_data item1, item2, sum;

while (cin >> item1 >> item2) { // прочитать две транзакции

 try {

  sum = item1 + item2; // вычислить их сумму

  // использовать сумму

 } catch (const isbn_mismatch &e) {

  cerr << e.what() << ": left isbn(" << e.left

       << ") right isbn (" << e.right << ")" << endl;

 }

}

Упражнения раздела 18.1.5

Упражнение 18.9. Определите описанные в этом разделе классы исключений приложения книжного магазина и перепишите составной оператор присвоения класса Sales_data так, чтобы он передавал исключение.

Упражнение 18.10. Напишите программу, использующую оператор суммы класса Sales_data для объектов с разными ISBN. Напишите две версии программы: способную обрабатывать исключении и не обрабатывающую их. Сравните поведение программ, чтобы ознакомиться с тем, что происходит при отсутствии обработки исключения.

Упражнение 18.11. Почему так важно, чтобы функция what() не передавала исключений?

Больше книг — больше знаний!

Заберите 30% скидку новым пользователям на все книги Литрес с нашим промокодом

ПОЛУЧИТЬ СКИДКУ