С++ обзор языка


Обработка исключительных ситуаций



страница10/10
Дата22.06.2019
Размер1.54 Mb.
1   2   3   4   5   6   7   8   9   10

15. Обработка исключительных ситуаций
15.1. Обработка ситуаций
Механизм обработки ситуаций дает способ передачи управления из точки выполнения программы в расположенную выше по управлению точку, в которой определена реакция на ситуацию (exception handler). Главная идея состоит в том, что функция, сталкивающаяся с неразрешимой проблемой, объявит исключительную ситуацию, в надежде на то, что вызвавшая ее (прямо или косвенно) функция может решить проблему. Реакция будет вызвана только в случае исполнения выражения-возбуждения-ситуации внутри так называемого блока-с-контролем или в функциях, вызванных из этого блока.
блок-с-контролем:

try составной-оператор список-реакций

список-реакций:

реакция список-реакций opt

реакция:


catch (объявление-ситуации) составной-оператор

объявление-ситуации:

список-спецификаторов-типа описатель opt

список-спецификаторов-типа абстрактный-описатель



. . .

выражение-возбуждения-ситуации:



throw выражение opt
Блок-с-контролем является оператором (6). Выражение-возбуждения-ситуации является унарным-выражением типа void (5). Выражение-возбуждения-ситуации иногда еще называют точкой возникновения (возбуждения) ситуации (throw-point). О части программы, в которой исполнилось выражение-возбуждения-ситуации, говорят, что в ней возникла ситуация (она возбудила ситуацию); часть программы, на которую вследствие этого передается управление, называется реакцией на ситуацию (handler).
Рассмотрим, каким образом можно определить и обработать ошибки диапазона, возникающие в классе Vector.
class Vector {

int* p;

int* sz;

public:

class Range { }; // класс ситуаций
int& operator[] (int i);

// . . .

};
Объекты класса Range предназначены для использования в качестве исключений и возбуждать последние следующим образом:
int& Vector::operator[] (int i)

{

if (0<=i && i

throw Range();

}
Функция, нуждающаяся в обнаружении выхода за диапазоны индекса, должна поместить вызов функции в блок-с-контролем с реакцией на ситуацию. Например:
void f(Vector& v)

{

// . . .

try {

do_something(v);

}

catch (Vector::Range) {

// реакция на исключение типа Vector::Range

// так как do_something() вызвало ситуацию, делаем что-то другое,

// попадаем сюда только, если обращение к do_something() приводит

// к вызову Vector::operator[] (i) с плохим индексом

}

// . . .

}
Конструкция
catch ( /* . . . */) { }
представляет собой реакцию на ситуацию. Она может использоваться только сразу за блоком-с-контролем или непосредственно за другой реакцией (если есть несколько реакций разных типов).
Процесс возбуждения и перехвата исключения требует просмотра цепочки вызова функций вверх от точки возникновения исключения, пока не будет найден try-блок с соответствующей реакцией. Если таким образом не будет найдено подходящая реакция, программа завершается аварийно.
15.2. Возбуждение ситуации
При возбуждении ситуации управление передается на реакцию. При этом подается объект, и тип этого объекта определяет, какие реакции могут перехватить данную ситуацию. Например,
throw "Help!";
может быть перехвачена некоторой реакцией на тип char*:
try {

// . . .

}

catch (const char* p) {

// здесь реакция на ситуацию типа литерной строки

}
а ситуация

class Overflow {

// . . .

public: Ovrflow(char, double, double);

};
void f(double x)

{

// . . .

throw Overflow('+', x, 3.45e107);

}
может быть перехвачена реакцией
try {

// . . .

f(1.2);

// . . .

}

catch (Overflow& oo) {

// здесь реакция на ситуацию типа Overflow

}
При возникновении ситуации управление передается на ближайшую реакцию соответствующего типа; "ближайшая" - та, в чей блок-с-контролем управление попало в последний раз; "соответствующий тип" определяется в 15.4.
Выражение-возбуждения-ситуации создает временный объект, тип которого статически определяется операндом throw, и затем инициализирует им переменную соответствующего типа, названную в реакции.
Операнд throw трактуется как параметр в вызове функции (5.2.2) или операнд оператора return, за исключением ограничений отождествления типов, описанных в 15.4.
Выражение-возбуждения-ситуации без операнда повторно возбуждает обрабатываемую ситуаци. Выражение-возбуждения-ситуации без операнда может появиться только в реакции или в функции, явно или неявно вызванной из реакции. Например, код, который нужно выполнить при возникновении какой-либо ситуации, не обрабатывая ее полностью, мог бы быть написан так:
try {

// . . .

}

catch (...) { // перехват ситуации

// частичная обработка ситуации

throw; // передача ситуации некоторой другой реакции
15.3. Конструкторы и деструкторы
Объект не считается созданным, пока не закончит работу его конструктор. Объект, состоящий из подъобектов, считается созданным после создания всех его подобъектов. Хорошо написанный конструктор должен убедиться в полноте и правильности созданного объекта и в противном слугае восстановить состояние системы, существовавшее до его вызова.
Исключения дают решение проблемы, как сообщать об ошибках в конструкторе. Поскольку конструктор не возвращает отдельное значение, которое могло бы быть проверено его вызывющим, можно предложить:
1) возвратить объект в плохом состоянии и доверить пользователю проверку

состояния;

2) присвоить "ошибочное" значение некоторой нелокальной переменной.
Исключения позволяют передать из констрктора информацию о неудаче при создании объекта. Пример:
class Vector {

int* p;

int* sz;

public:

class Range { }; // класс ситуаций "ошибка индексации"

class Size { }; // класс ситуаций "неприемлемый размер"

Vector (int sz);

int& operator[] (int i);

// . . .

};
Vector::Vector(int sz)

{

if (sz<0 || max

// . . .

}
Участок программы, создающий векторы, может теперь обрабатывать ошибки типа Size, например:
Vector* f(int i)

{

Vector* p;

try { p = new Vector v(i); }

catch(Vector::Size) {

// обработка ошибок, вызванных плохим размером вектора

}

// . . .

return p;

}
При передаче управления из точки возникновения ситуации на реакцию для всех автоматических объектов, построенных после входа в блок-с-контролем, вызываются деструкторы.
Процесс вызова деструкторов для автоматических объектов, построенных на пути от блока-с-контролем до выражения-возбуждения-ситуации, называется сверткой стека.
15.4. Распознование ситуации
Реакция с типом T, const T, T& или const T& отождествляется для выражения-возбуждения-ситуации с объектом типа Е, если:
а) Т и Е один и тот же тип, или

б) Т - доступный (4.6) в точке возбуждения ситуации базовый класс для Е, или

в) Т - тип указателя и Е - тип указателя, который можно преобразовать в Т

стандартным преобразованием указателя (4.6) в точке возбуждения ситуации. Пример:


class Matherr {/* . . . */ virtual vf(); };

class Overflow: public Matherr {/* . . . */ };

class Underflow: public Matherr {/* . . . */ };

class Zerodivide: public Matherr {/* . . . */ };
void f()

{

try { g() }

catch (Overflow oo) {

// . . .

}

catch (Matherr mm) {

// . . .

}

}
Здесь реакция на Overflow будет перехватывать ситуации типа Overflow, а реакция на Matherr будет перехватывать ситуации типа Matherr, а также все открытые производные типы от Matherr, включаяя Underflow и Zerodivide (кроме, конечно Overflow).
Реакции блока-с-контролем проверяются на применимость в порядке их написания. Ошибкой является расположение реакции на базовый класс перед реакцией на его производный класс, потому что в таком случае реакция на производный класс никогда не получит управления.
В объявлении-ситуации многоточие (. . .) играет примерно ту же роль, что и в параметрах объявления функции: оно дает отождествление для любой ситуации. Реакция с многоточием, если она есть, должна быть последней в ее блоке-с-контролем.
Если среди реакций блока-с-контролем подходящей не найдено, поиск реакции прододжается в блоке-с-контролем, динамически охватывающем данный блок-с-контролем.
Если нигде не нашлось подходящей реакции, вызывается функция terminate (15.6).
Если возникает ситуация в реакции, она обрабатывается тем участком программы, который вызвал данный блок-с-контролем.
15.5. Спецификация ситуаций
Возбуждение и перехват ситуации влияют на взаимодействие функции с другими функциями. Язык предоставляет возможность перечислить множество ситуаций, которые функция может прямо или косвенно возбудить, как часть объявления функции. Спецификации ситуаций следует за описателем функции:
спецификация-ситуаций:

throw (список-типов opt)

список-типов:

имя-типа


список-типов, имя-типа
Пример: void f() throw (X, Y) { /* . . . */ }.
Здесь указывается, что f() может возбуждать исключения типов Х и У, а также производных от них, но никакие другие.
Закон Мерфи для обработки ситуаций читается так: "Любая ситуация, которая может возникнуть, возникнет"; так что подобное объявление функции следует читать как "f() возбуждает Х и У".
Когда функция сообщат что-нибудь о ее искдючениях, это позволяет дать гарантию вызывающей ее функции; если при исполнении функции делается попытка вызвать ситуацию, не названную в списке ее ситуаций (т.е. нарушить грантию), вызывается функция unexpected (15.6).
Функция без спецификации-ситуаций может возбудить любую ситуацию.
Функция с пустой спецификацией-ситуаций не должна возбуждать ситуаций.
Спецификации-ситуаций не является часть тела функции.
15.6. Специальные функции
Функция terminate вызывается, когда не находитя реакция на ситуацию. Она в свою очередь вызывает функцию abort(), которая прекращает выполнение программы.
Функция unexpected вызывается, когда функция со спецификацией-ситуаций возбуждает непредусмотренную ситуацию. Эта функция также в конечном итоге вызывает функцию abort().
15.7. Ситуации и доступ
Семантика обработки ситуации идентична семантике получения аргумента функции. Поэтому формальные параметры в объявлении-ситуации подчиняются тем же правилам доступа, что и формальные параметры функции, в которой находитя это объявление.
Объект может быть послан выражением-возбуждения-ситуации, если его можно копировать и уничтожать в контексте, в котором находится это выражение.

16. Потоки
16.1. Ввведение
С++ не обеспечивает встроенных средств ввода/вывода, но они создаются посредством самого языка. Описываемая стандартная библиотека потоков ввода/вывода обеспечивает гибкий и эффективный метод символьного ввода/вывода целых чисел, чисел с плавающей запятой и символьных строк: а также простую модель ее расширения для ввода/ данных пользовательских типов. Ее пользовательский интерфейс находится в файле .
Традиционно средства ввода/вывода разрабатывались исключительно для небольшого числа встроенных типов данных. Однако в программах на С++ обычно используется много типов, определенных пользователем, и требуется вводить/выводить данные этих типов. Ничье решение этой задачи не может угодить всем, поэтому у пользователя должна быть возможность задавать альтернативные средства ввода/вывода и расширять стандартные средства ввода/вывода применительно к требованиям приложения. Обоснованным является также требование, что средства ввода/вывода для С++ должны обеспечиваться в С++ посредством только тех возможностей, которые доступны программирующему на этом языке.
Существует много независимых реализаций библиотеки потоков ввода/вывода, и описываемый ниже набор средств - это только часть возможностей, которые можно найти в библиотеке. В файле определяется интерфейс с библиотекой потоков. Более ранняя версия этой библиотеки использует файл . Там, где есть обе версии, в файле определяется полный набор возможностей, тогда как - это только подмножество, которое образует интерфейс с прежней, менее обширной библиотекой потоков.
10.2. Вывод
Единообразный с гарантией типового контроля вывод данных как встроенных, так и пользовательских типов обеспечивается использованием одного совмещенного имени для множества операций вывода. Пример:
put(cerr, "x = "); // cerr - поток вывода ошибок

put(cerr, x);

put(cerr, '\n');
В каждом случае тип параметра определяет, какая из put должна вызываться. Более лаконичный вариант вывода - использование совмещенного знака операции "<<", обозначающего операцию "поместить в", что позволяет выводить одним оператором сразу несколько объектов, например, если х есть int со значением 123, то оператор
cerr << "x =' << x << '\n';
выведет в стандартный поток ошибок cerr строку
x = 123
и символ новой строки. Аналогично, если х имеет тип complex и значение

(1, 2.4), оператор выведет


х = (1, 2.4)
Этот метод можно использовать в тех случаях, когда для х определена операция "<<", а определить ее можно в любом типе. Операция "<<" имет достаточно низкий приоритет для того, чтобы использовать в качестве операндов арифметические выражения без скобок, например:
cout << "a*b+c" << a*b+c << '\n';
Когда необходимо выводить значения выражений, содержащих менее приоритетные операции, следует использовать скобки, например:
cout << "a^b|c" << (a ^ b | c) << '\n';
Операция сдвига влево также может быть использована в выводимом выражении, но, разумеется, она должна быть заключена в скобки:
cout << "a<
10.2.1. Вывод данных встроенных типов
Для вывода данных встроенных типов определен следующий класс ostream:
class ostream : public virtual ios {

// . . .

public:

ostream& operator << (const char*); // строки

ostream& operator << (char);

ostream& operator << (short i) {return *this << int(i);};

ostream& operator << (int);

ostream& operator << (long);

ostream& operator << (double);

ostream& operator << (const void); // указатели

// . . .

};
Естественно, что в потоке ostream содержится также ряд операций "<<" для работы с беззнаковыми типами.
Согласно определению каждая операция "<<" возвращает ссылку на поток, для котороко она вызвана и потому к нему можно применить другой оператор "<<" как показано выше. Отсюда, в частности, следует, что когда один оператор выводит несколько значений, они будут выводится в ожидаемом порядке.
Операция ostream::operator << (int) печатает целые числа, операция

ostream::operator << (char) - символы и т.д.; операция

ostream::operator << (const void) печатает значение указателя в той форме, которая присуща адресам на данной машине. Пример:
main()

{

int i = 0;

int* p = new int(1);

cout << "локальная " << &i << ", динамическая " << p << '\n';
может напечатать
локальная 0x7fffead0, динамическая 0x500c.
10.2.2. Вывод значений пользовательских типов
Рассмотрим пользовательский тип
class complex {

double re, im;

public:

complex(double r = 0, double i = 0) { re = r; im = i;}

double real(complex& a) {return a.re;}

double imag(complex& a) {return a.im;}

complex operator + (complex, complex);

complex operator - (complex, complex);

complex operator * (complex, complex);

complex operator / (complex, complex);

// . . .

};
Операция "<<" для нового типа complex может быть определена следующим образом:
ostream& operator << (ostream& s, complex z)

{

return s << '(' << real(z) << ',' << imag(z) << ')';

}
и использована стандартным образом:
main()

{

complex x(1, 2);

cout << "x = " << x << '\n';

}
со следующим результатом:
x = (1, 2)
10.3. Ввод
Ввод аналогичен выводу. Имеется класс istream, который предоставляет операцию ">>" (взять из) для стандартных типов. Эта операция может затем переопределяться для пользовательских типов.
10.3.1. Ввод значений встроенных типов
Класс istream определяется следующим образом:
class istream : public virtual ios {

// . . .

public:

istream& operator >> (char*); // строки

istream& operator >> (char&); // символы

istream& operator >> (short&);

istream& operator >> (int&);

istream& operator >> (long&);

istream& operator >> (float&);

istream& operator >> (double&);

// . . .

};
Каждая из операций описывается в следующем духе:
istream& istream::operator >> (T& tvar);

{

// игнорировать пропуски

// неким образом считать значение типа Т в 'tvar'

return *this

}
Таким образом можно ввести последовательность целых чисел, разделенных пропусками, в некоторый вектор целых чисел:
int readints(Vector v)

// возвращает количество введенных чисел

{

for (int i = 0; i < v.size(); i++)

{

if (cin >> v[i]) continue;

return i;

};

// слишком много чисел для данного размера вектора

}
Первое встреченное нецелое значение на входе вызовет неудачное завершение операции ввода и тем самым завершит цикл ввода. Например, при вводе последовательности
1 2 3 4 5. 6 7 8
функция readints считала бы 5 первых значений, оставив точку как следующий вводимый символ. Пропуском являются символы пробела, табуляции, перевода строки, возврата каретки и конца страницы.
Кроме операции ">>", можно пользоваться также следующими функциями ввода:
class istream : public virtual ios {

// . . .

public:

istream& get(char*); // символ

istream& get(char*, int, char = '\n'); // строка

}
Эти функции не выделяют пропуски из других символов. Функция get(char*) вводит в свой аргумент очередной символ, а функция get(char* p, int n, char = '\n') - строку длиною максимум n-1 символ в вектор, начинающийся с p (n-ым символом будет завершающий 0); третий аргумент задает символ останова, которым по умолчанию является '\n'.
Стандартный заголовочный файл содержит описания нескольких функций, полезных при обработке ввода:
int isalfa(char) // 'a' .. 'z' 'A' .. 'Z'

int isupper(char) // 'A' .. 'Z'

int islower(char) // 'a' .. 'z'

int isdigit(char) // '0' .. '9'

int isxdigit(char) // '0' .. '9' 'a' .. 'f' 'A' .. 'F'

int isspace(char) // пробел, табуляция, перевод строки, возврат каретки,

// конец страницы

int iscntr(char) // управляющий символ (ASCII 0 .. 31, 127)

int ispunct(char) // символ пунктуации; ни один из перечисленных выше

int isalnum(char) // isalfa(char) | isdigit(char)

int isprint(char) // печатаемый: ASCII ' ' .. '~'

int isgraph(char) // isalfa(char) | isdigit(char) | ispunct(char)

int isascii(char) {return 0 <= c && c <= 127; }
Функция eatwhite, считывающая подряд идущие пропуски из потока, может теперь быть описана следующим образом:
istream& eatwhite(istream& is)

{

char c;

while is.get(c)) {

if (isspace(c) == 0) {

is.putback(c);

breack;

}

}

return is;

}
Здесь использована функция putback(c), которая возвращает символ назад в поток для последующего считывания.
10.3.2. Состояния потока
Каждый поток обладает состоянием, и обработка ошибок и нештатных ситуаций осуществляется путем установки и проверки этого состояния.
Состояние потока можно проверить посредством следующих функций класса ios.
class ios { // базовый класс для istream и ostream

// . . .

public:

int eof()const // конец файла

int fail()const // следующая операция потерпит неудачу

int bad()const // поток испорчен

int good()const // следующая операция может пройти успешно

// . . .

enum io_state {

goodbit = 0;

eofbit = 1;

failbit = 2;

badbit = 4;

};

int rdstate() const; // совокупность битов, задающих состояние потока

void clear(int i = 0); // установить состояние потока

}
Состояние good() или eof() означает, что последняя операция ввода прошла успешно. В состоянии good() следующая операция ввода может пройти успешно, в противном случае она закончится неудачей.
Если делается попытка читать в переменную v и операция заканчивается неудачей, значение v не должно измениться (оно и не изменится, если v имеет тип, для которого есть компонентная операция ввода в потоке istream).
Состояние fail() предполагает, что поток не испорчен и никакие символы в нем не потеряны; в состоянии bad() может быть все, что угодно. Две последних функции обычно используются только разработчиками операций ввода.
Константы перечисления io_state определяют целые значения, сопоставленные этим состояниям.
Состояния потока могут быть проверены, например, следующим образом:
int s = cin.rdstate(); // совокупность io_state битов потока cin
if (s & ios::goodbit) {

// последняя операция над cin прошла успешно

}

else if (s & ios::badbit) {

// возможно, символы cin потеряны

}

else if (s & ios::failbit) {

// возможн была ошибка, но не слишком опасная

}

else if (s & ios::eofbit) {

// конец файла

}
Делать подобную проверку после каждой операции ввода в программе не очень удобно, но предполагается, что приведенные средства могут быть использованы в механизме обработки ситуаций.
10.3.3. Ввод данных пользовательских типов
Операция ввода для пользовательского типа определяется точно так же, как и операция вывода, но при этом важно, чтобы второй параметр имел тип ссылки. Пример:
istream operator >> (istream& s, complex a)

/* форматы для ввода комплексных чисел; f обозначает float:

f

(f)

(f, f)

*/

{

double re = 0, im = 0;

char c = 0;
c >> c;

if (c == '(' {

s >> re >> c;

if (c == ',') s >> im >> c;

if (c != ')') s.clear(ios::badbit); // установить состояние непригодности

}

else {

s.putback(c);

s >> re;

};
if (s) a = complex(re, im);

return s;

}
Эта функция будет обрабатывать большую часть возможных ошибок. Локальная переменная с инициализируется, чтобы ее значение случайно не оказалось "(" после провала операции ввода. Завершающая проверка состояния потока нужна для гарантии, что все прошло хорошо.




Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   10


База данных защищена авторским правом ©vossta.ru 2019
обратиться к администрации

    Главная страница