Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
прога_билеты.docx
Скачиваний:
2
Добавлен:
01.03.2025
Размер:
2.98 Mб
Скачать

Исключения

Какие методы сообщения об ошибках есть в языке?

  • Возврат кода ошибки

  • Бросание исключения

Генерирование и перехват исключений

Если в некотором разделе кода обнаруживается исключительная ситуация, генерируется исключение.

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

#include <stdexcept>

double divideNumbers (double inNutnerator, double inDenominator) {

if (inDenominator ==0) throw std::exception();

return (inNumerator / inDenominator);

}

При выполнении инструкции trow функция будет немедленно завершена без возврата какого бы то ни было значения. Если вызов функции заключить в try-catch блок, как показано в следующем коде, то инициатор вызова получит исключение и сможет его обработать.

#include <iostream>

#include <stdexcept>

int main(int argc, char** argv) {

try {

std::cout « divideNumbers(2.5, 0.5) « std::endl;

std::cout « divideNurnbers (2.3, 0) « std::endl;

} catch (std::exception exception) {

std::cout « "Исключение перехвачено! " << std::endl;

}

}

Throw-списки.

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

void readlntegerFile(const strings fileName, vector<int>& dest) { … }

преобразуем в:

void readlntegerFile(const strings fileName, vector<int>& dest)

throw (invalid argument, runtime error) { … }

В throw-списке просто перечисляются типы исключений, которые могут быть сгенерированы из этой функции. Обратите внимание на то, что throw-список должен быть включен и в объявление функции, если её реализация находится в коде отдельно.

Функция без throw-списка может генерировать исключения любого типа.

Функция с пустым throw-списком не должна генерировать исключения вообще.

Хотя throw-списки не мешают функциям генерировать исключения не указанных в них типов, они не позволяют таким исключениям выйти заграницы функций.

Если функция генерирует исключение, которое отсутствует в ее throw-списке, С++ вызывает специальную функцию unexpected(). Встроенная реализация функции unexpected() просто содержит обращение к функции terminate(). Но точно так же, как вы устанавливали собственный обработчик типа terminate_handler, вы сможете установить собственный обработчик типа unexpected_handler. В отличие от обработчика terminate_handler, в обработчик unexpected_handler вы можете действительно заложить выполнение действий, отличных от простого завершения программы.

Изменение throw-списков в переопределенных методах

При переопределении виртуального метода в подклассе можно изменить throw-список, если вы сделаете его более ограничительным, чем throw-список в суперклассе. Более ограничительными считаются следующие изменения:

  • Удаление исключений из списка.

  • Добавление подклассов исключений, которые имеются в throw-списке суперкласса.

Следующие изменения не считаются более ограничительными:

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

  • Полное удаление throw-списка

Если вы изменяете throw-списки при переопределении методов, помните, что любой код, который вызывается версией метода, определенной суперклассом, должен быть способен вызвать версию метода подкласса. Это означает, что исключения добавлять нельзя.

Обработка ошибок в конструкторах и деструкторах

Несмотря на невозможность конструктора возвращать значение, вы можете из него сгенерировать исключение.

С помощью исключения нетрудно уведомить клиента о результате выполнения конструктора: успешном или нет.

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

Рассмотрим пример конструктора класса, модифицированного с использованием обработки исключения:

GameBoard::GameBoard(int inWidth, int inHeight) throw (bad_alloc): mWidth(inWidth), mHeight(inHeight) {

int i, j;

mCells = new GamePiece* [mWidth];

try {

for (i = 0; i <mWidth; i++) {

mCells[i] = new GamePiece[mHeight];

}

} catch (...) {

/* Освобождаем память, которую мы уже выделили, поскольку деструктор для объекта никогда не будет вызван. Верхняя граница Еог-цикла соответствует индексу последнего элемента в массиве тСе11в, который мы пытались создать динамически (но попытка "проваливается"). Мы должны освободить память с помощью указателей, которые мы успели сохранить в массиве до возникновения исключения, используя все "занятые" индексы этого массива */

for (j = 0; j < i; j++) {delete [] mCells [j ] ;}

delete[] mCells;

//Любое исключение приводим к типу bad_alloc

throw bad alloc();

}

}

Необходимо обрабатывать все ошибочные ситуации, возникающие в самих деструкторах. Причины:

  • Деструкторы могут работать, пока обрабатывается другое исключение. Если сгенерировать исключение из деструктора в то время, пока еще активно другое исключение, программа будет завершена.

  • Подумайте о своих клиентах. Клиенты не вызывают деструкторы явно: они вызывают оператор delete, который вызывает деструктор. Если вы сгенерируете исключение из деструктора, то какие действия должен, по вашему, предпринять?

  • Деструктор — это ваш шанс освободить память и другие ресурсы, используемые объектом. Если вы утратите этот шанс в результате преждевременного выхода из функции деструктора из-за исключения, вы никогда не сможете вернуться назад и освободить ресурсы.

Поэтому нужно стараться делать деструкторы как модно более простыми. Например, использовать только функции delete и delete[], которые не могут кинуть исключения.