logo
Языки программирования

11.3. Исключения в Ada

В языке Ada определен очень простой механизм обработки исключений, ко-

торый послужил моделью для других языков.

В Ada есть четыре предопределенных исключения:

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

Storage_Error (ошибка памяти). Недостаточно памяти.

Program_Error (программная ошибка). Нарушение правил языка, напри-

мер выход из функции без выполнения оператора return.

Tasking_Error (ошибка задачи). Ошибки, возникающие при взаимодейст- вии задач

(см. гл. 12).

Конечно, Constraint_Error — наиболее часто встречающееся исключение, связанное со строгим контролем соответствия типов в языке Ada. Кроме того,

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

так же, как и предопределенные исключения.

Когда исключительная ситуация наступает, в терминологии языка Ada —возбуждается (raised), вызывается блок кода, называемый обработчиком иск­лючения (exeption handler). В отличие от PL/I вызов обработчика завершает включающую процедуру. Так как обработчик не возвращается к нормальному вычислению, нет никаких помех для оптимизации. В отличие от обработчи­ков глобальных ошибок в С, обработка исключительных ситуаций в Ada чрез­вычайно гибкая, потому что обработчики исключений могут быть привязаны к любой подпрограмме:

procedure Main is

procedure Proc is

P: Node_Ptr;

begin

P := new Node; -- Может возбуждаться исключение

Statement_1; -- Пропускается, если возбуждено исключение

exception

when Storage_Error =>

-- Обработчик исключения

end Proc; begin Proc; Statement_2; — Пропускается, если исключение распространилось

из Proc

exception

when Storage_Error =>

-- Обработчик исключения

end Main;

После последнего исполняемого оператора подпрограммы ключевое слово exception вводит последовательность обработчиков исключений — по одному для каждого вида исключений. Когда возбуждается исключение, процедура покидается, и вместо нее выполняется обработчик исключения. Когда обра­ботчик завершает свою работу, выполняется нормальное завершение процедуры. В приведенном примере программа выделения памяти может по­родить исключительную ситуацию Storage_Error, в этом случае Statement_1 пропускается, и выполняется обработчик исключения. После завершения об­работчика и нормального завершения процедуры главная программа продол­жается с оператора Statement_2.

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

• Если исключительная ситуация не обработана внутри процедуры, попытка ее выполнения оставляется, и исключительная ситуация возбуждается снова в точке вызова. При отсутствии обработчика в Proc исключение повторно было бы возбуждено в Main, оператор Statement_2 был бы пропущен и выполнен обработчик в Main.

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

• У программиста есть выбор: возбудить то же самое или другое исклю­чение в точке вызова. Например, мы могли бы перевести предопре­деленное исключение типа Storage_Error в исключение, определенное в прикладной программе. Это делается с помощью оператора rais в обра­ботчике:

exception

when Storage_Error =>

… -- Обрабатывается исключение, затем

raise Overflow; --Возбуждается исключение Overflow в точке вызова

Обработчик для others может использоваться, чтобы обработать все исклю­чения, которые не упомянуты в предыдущих обработчиках.

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

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

exception

when Ех_1 | Ех_2 | Ех_3 =>

--Какое именно исключение произошло?

Язык Ada 95 позволяет обработчику исключительной ситуации иметь па­раметр:

exception

when Ex: others =>

Всякий раз при возбуждении исключения параметр Ех будет содержать информацию, идентифицирующую исключение, а предопределенные проце­дуры позволят программисту отыскать информацию относительно исклю­чения. Эта информация также может быть определена программистом (см. справочное руководство по языку, раздел 11.4.1).

Реализация

Реализуются обработчики исключений очень эффективно. Процедура, кото­рая содержит обработчики исключений, имеет дополнительное поле в записи активации с указателем на обработчики (см. рис. 11.1). Требуется только одна команда при вызове процедуры, чтобы установить это поле, вот и все издержки при отсутствии исключений.

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

11.4. Исключения в C++

Обработка исключений в C++ во многом сходна с той, которая применяется в языке Ada, а именно, исключение можно явно возбудить, обработать соот­ветствующим обработчиком (если он есть), после чего блок (подпрограмма) окажется завершенным. Отличия в следующем:

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

• Исключения идентифицируются типом параметра, а не именем. Имеет­ся специальный эквивалент синтаксиса others для обработки исклю­чений, не упомянутых явно.

• Можно создавать семейства исключений, используя наследование (см. гл. 14).

• Если в языке Ada для исключения в программе не предусмотрен обра­ботчик, то вызывается системный обработчик. В C++ программист мо­жет определить функцию terminate(), которая вызывается, когда исклю­чение не обработано.

В следующем примере блок try идентифицирует область действия последова­тельности операторов, для которых обработчики исключений (обозначенные как catch-блоки) являются активными. Throw-оператор приводит к возбужде­нию исключений; в этом случае оно будет обработано вторым catch-блоком, так как строковый параметр throw-оператора соответствует параметру char* второго catch-блока:

void proc()

{

… // Исключения здесь не обрабатываются

try {

throw "Invalid data"; // Возбудить исключение

}

catch (int i) {

… // Обработчик исключения

}

catch (char *s) {

… // Обработчик исключения

}

catch (...) { // Прочие исключения

…. // Обработчик исключений

}

}

Как в Ada, так и в C++ допускается, чтобы обработчик вызывался для исклю­чения, которое он не может видеть, потому что оно объявлено в теле пакета (Ada), или тип объявлен как private в классе (C++). Если исключение не обра­ботано и в others (или ...), то оно будет неоднократно повторно возбуждаться до тех пор, пока, наконец, с ним не обойдутся как с необработанным исклю­чением. В C++ есть способ предотвратить такую неопределенность поведе­ния с помощью точного объявления в подпрограмме, какие исключительные ситуации она готова обрабатывать:

void proc() throw (t1 , t2, t3);

Такая спецификация исключений (exception specifications) означает, что отсутствующие в списке исключения, которые, возможно, будут возбуж­даться, но не будут обрабатываться в ргос (или любой подпрограмме, вызван­ной из ргос), немедленно вызывут глобально определенную функцию unex-pectedQ вместо того, чтобы продолжать поиск обработчика. В больших системах эта конструкция полезна для документирования полного интерфейса подпрограмм, включая исключения, которые будут распространяться.