Чтение онлайн

ЖАНРЫ

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

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

Рассмотрим пример.

int f(int x, int y, int z)

{

int area1 = area(x,y);

if (area1<=0) error("Неположительная площадь");

int area2 = framed_area(1,z);

int area3 = framed_area(y,z);

double ratio = double(area1)/area3;

// ...

}

Вы заметили ошибку? Такие ошибки трудно выявить, так как

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

ПОПРОБУЙТЕ

Выполните эту программу при разных значениях. Выведите на печать значения переменных

area1
,
area2
,
area3
и
ratio
. Вставьте в программу больше проверок разных ошибок. Вы уверены, что перехватите все ошибки? Это вопрос без подвоха; в данном конкретном примере можно ввести правильный аргумент и перехватить все возможные ошибки.

Существует другой способ решить описанную проблему: использовать исключения (exceptions).

5.6. Исключения

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

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

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

Любая функция, прямо или косвенно вызывающая данную функцию, может перехватить созданное исключение с помощью оператора

catch
, т.е. указать, что следует делать, если вызываемый код использовал оператор
throw
. Функция расставляет ловушки для исключения с помощью блока
try
(мы опишем его в следующих разделах), перечисляя виды исключений, которые она хочет обработать в своих разделах
catch
блока
try
. Если ни одна из вызывающих функций не перехватила исключение, то программа прекращает работу.

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

5.6.1. Неправильные аргументы

Рассмотрим вариант функции

area
, использующий исключения.

class Bad_area { }; // Тип, созданный специально для сообщений

// об ошибках,

// возникших в функции area

// Вычисляет площадь прямоугольника;

// при неправильном аргументе генерирует исключение Bad_area

int area(int length, int width)

{

if (length<=0 || width<=0) throw Bad_area;

return length*width;

}

Иначе говоря, если аргументы правильные, то программа всегда возвращает площадь прямоугольника, а если нет, то выходим из функции

area
с помощью оператора
throw
, надеясь найти ответ в одном из разделов
catch
.
Bad_area
— это новый тип, предназначенный исключительно для генерирования исключений в функции
area
, так, чтобы один из разделов
catch
распознал его как исключение, сгенерированное функцией
area
. Типы, определенные
пользователями (классы и перечисления), обсуждаются в главе 9. Обозначение
Bad_area
означает “Создать объект типа Bad_area”, а выражение
throw Bad_area
означает “Создать объект типа
Bad_area
и передать его (
throw
) дальше”.

Теперь функцию можно написать так:

int main

try {

int x = –1;

int y = 2;

int z = 4;

// ...

int area1 = area(x,y);

int area2 = framed_area(1,z);

int area3 = framed_area(y,z);

double ratio = area1/area3;

}

catch (Bad_area) {

cout << "Ой! Неправильный аргумент функции area\n";

}

Во-первых, этот фрагмент программы обрабатывает все вызовы функции

area
как вызов из модуля
main
, так и два вызова из функции
framed_area
. Во-вторых, обработка ошибки четко отделена от ее выявления: функция
main
ничего не знает о том, какая функция выполнила инструкцию
throw Bad_area
, а функция
area
ничего не знает о том, какая функция (если такая существует) должна перехватывать исключения
Bad_area
, которые она генерирует. Это разделение особенно важно в крупных программах, написанных с помощью многочисленных библиотек. В таких программах ни один человек не может обработать ошибку, просто поместив некоторый код в нужное место, поскольку никто не может модифицировать код одновременно в приложении и во всех библиотеках.

5.6.2. Ошибки, связанные с диапазоном

Большинство реальных программ работает с наборами данных. Иначе говоря, они используют разнообразные таблицы, списки и другие структуры данных. В контексте языка С++ наборы данных часто называют контейнерами (containers). Наиболее часто используемым контейнером стандартной библиотеки является тип vector, введенный в разделе 4.6.

Объект типа

vector
хранит определенное количество элементов, которое можно узнать с помощью его функции-члена
size
. Что произойдет, если мы попытаемся использовать элемент с индексом, не принадлежащим допустимому диапазону
[0:v.size]
? Обычное обозначение
[low:high]
означает, что индексы могут принимать значения от low до
high-1
, т.е. включая нижнюю границу, но исключая верхнюю.

Прежде чем ответить на этот вопрос, необходимо ответить на другой: “Как это может быть?” Помимо всего прочего, известно, что индекс вектора

v
должен лежать в диапазоне
[0:v.size]
, поэтому достаточно просто убедиться в этом!

Легко сказать, но трудно сделать. Рассмотрим следующую вполне разумную программу:

vector<int> v; // вектор целых чисел

int i;

while (cin>>i) v.push_back(i); // вводим значения в контейнер

for (int i = 0; i<=v.size; ++i) // печатаем значения

cout << "v[" << i <<"] == " << v[i] << endl;

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

Поделиться с друзьями: