Бьерн Страуструп - Язык программирования С++. Главы 8-10 - Производные особые ситуации
ОГЛАВЛЕНИЕ
9.3.2 Производные особые ситуации
Если для обработки особых ситуаций мы используем иерархию классов, то, естественно, каждый обработчик должен разбираться только с частью информации, передаваемой при особых ситуациях. Можно сказать, что, как правило, особая ситуация перехватывается обработчиком ее базового класса, а не обработчиком класса, соответствующего именно этой особой ситуации. Именование и перехват обработчиком особой ситуации семантически эквивалентно именованию и получению параметра в функции. Проще говоря, формальный параметр инициализируется значением фактического параметра. Это означает, что запущенная особая ситуация "низводится" до особой ситуации, ожидаемой обработчиком. Например:class Matherr {При входе в обработчик Matherr особая ситуация m является объектом Matherr, даже если при обращении к g() была запущена Int_overflow. Это означает, что дополнительная информация, передаваемая в
// ...
virtual void debug_print();
};
class Int_overflow : public Matherr {
public:
char* op;
int opr1, opr2;;
int_overflow(const char* p, int a, int b)
{ cerr << op << '(' << opr1 << ',' << opr2 << ')'; }
};
void f()
{
try {
g();
}
catch (Matherr m) {
// ...
}
}
Int_overflow, недоступна.
Как обычно, чтобы иметь доступ к дополнительной информации можно использовать указатели или ссылки. Поэтому можно было написать так:
int add(int x, int y) // сложить x и y с контролемЗдесь последнее обращение к add приведет к запуску особой ситуации, который, в свою очередь, приведет к вызову Int_overflow::debug_print(). Если бы особая ситуация передавалась по значению, а не по ссылке, то была бы вызвана функция Matherr::debug_print().
{
if (x > 0 && y > 0 && x > MAXINT - y
|| x < 0 && y < 0 && x < MININT + y)
throw Int_overflow("+", x, y);
// Сюда мы попадаем, либо когда проверка
// на переполнение дала отрицательный результат,
// либо когда x и y имеют разные знаки
return x + y;
}
void f()
{
try {
add(1,2);
add(MAXINT,-2);
add(MAXINT,2); // а дальше - переполнение
}
catch (Matherr& m) {
// ...
m.debug_print();
}
}
Нередко бывает так, что перехватив особую ситуацию, обработчик решает, что с этой ошибкой он ничего не сможет поделать. В таком случае самое естественное запустить особую ситуациюснова в надежде, что с ней сумеет разобраться другой обработчик:
void h()Повторный запуск записывается как оператор throw без параметров. При этом снова запускается исходная особая ситуация, которая была перехвачена, а не та ее часть, на которую рассчитан обработчик
{
try {
// какие-то операторы
}
catch (Matherr) {
if (can_handle_it) { // если обработка возможна,
// сделать ее
}
else {
throw; // повторный запуск перехваченной
// особой ситуации
}
}
}
Matherr. Иными словами, если была запущена Int_overflow, вызывающая h() функция могла бы перехватить ее как Int_overflow, несмотря на то, что она была перехвачена в h() как Matherr и запущена снова:
void k()Полезен вырожденный случай перезапуска. Как и для функций, эллипсис ... для обработчика означает "любой параметр", поэтому оператор catch (...) означает перехват любой особой ситуации:
{
try {
h();
// ...
}
catch (Int_overflow) {
// ...
}
}
void m()Этот пример надо понимать так: если при выполнении основной части m() возникает особая ситуация, выполняется обработчик, которые выполняет общие действия по устранению последствий особой ситуации, после этих действий особая ситуация, вызвавшая их, запускается повторно.
{
try {
// какие-то операторы
}
catch (...) {
// привести все в порядок
throw;
}
}
Поскольку обработчик может перехватить производные особые ситуации нескольких типов, порядок, в котором идут обработчики в проверяемом блоке, существенен. Обработчики пытаются перехватить особую ситуацию в порядке их описания. Приведем пример:
try {Тип особой ситуации в обработчике соответствует типу запущенной особой ситуации в следующих случаях: если эти типы совпадают, или второй тип является типом доступного базового класса запущенной ситуации, или он является указателем на такой класс, а тип ожидаемой ситуации тоже указатель ($$R.4.6).
// ...
}
catch (ibuf) {
// обработка переполнения буфера ввода
}
catch (io) {
// обработка любой ошибки ввода-вывода
}
catch (stdlib) {
// обработка любой особой ситуации в библиотеке
}
catch (...) {
// обработка всех остальных особых ситуаций
}
Поскольку транслятору известна иерархия классов, он способен обнаружить такие нелепые ошибки, когда обработчик catch (...) указан не последним, или когда обработчик ситуации базового класса предшествует обработчику производной от этого класса ситуации ($$R15.4). В обоих случая, последующий обработчик (или обработчики) не могут быть запущены, поскольку они "маскируются" первым обработчиком.