Данные в классах исключений и присвоение имен объектам исключений

Довольно часто чтобы программа имела возможность отреагировать подобающим образом на неточность, полезно знать больше, чем легко тип появившегося исключения. Классы исключений — это такие же классы, как и каждые другие. Вы полностью вольно имеете возможность додавать каждые данные в эти классы, инициализировать их посредством конструктора и считывать их значения в любое время, как продемонстрировано в листинге 20.4.

Листинг 20.4. возвращение данных из объекта исключения

1: #include

2:

3: const int DefaultSize = 10;

4:

5: не сильный Array

6: {

7: public:

8: // конструкторы

9: Array(int itsSize = DefaultSize);

10: Array(const Array rhs);

11: ~Array() { delete [] pType;}

12:

13: // операторы

14: Array operator=(const Array);

15: int operator[](int offSet);

16: const int operator[](int offSet) const;

17:

18: // способы доступа

19: int GetitsSize() const { return itsSize; }

20:

21: // функция-приятель

22: friend ostream operator

23:

24: // определение классов исключений

25: class xBoundary { };

26: class xSize

27: {

28: public:

29: xSize(int size):itsSize(size) { }

30: ~xSize(){ }

31: int GetSize() { return itsSize; }

32: private:

33: int itsSize;

34: };

35:

36: class xTooBig : public xSize

37: {

38: public:

39: xTooBig(int size):xSize(size){ }

40: };

41:

42: class xTooSmall : public xSize

43: {

44: public:

45: xTooSmall(int size):xSize(size){ }

46: };

47:

48: class xZero : public xTooSmall

49: {

50: public:

51: xZero(int size):xTooSmall(size){ }

52: };

53:

54: class xNegative : public xSize

55: {

56: public:

57: xNegative(int size):xSize(size){ }

58: };

59:

60: private:

61: int *pType;

62: int itsSize;

63: };

64:

65:

66: Array::Array(int size):

67: itsSize(size)

68: {

69: if (size == 0)

70: throw xZero(size);

71: if (size 30000)

72: throw xTooBig(size);

73: if (size

74: throw xNegative(size);

75: if (size 10)

76: throw xTooSnall(size);

77:

78: pType = new int[size];

79: for (int i = 0; i

80: pType[i] = 0;

81: }

82:

83:

84: int Array::operator[] (int offSet)

85: {

86: int size = GetitsSize();

87: if (offSet = 0 offSet GetitsSize())

88: return pType[offSet];

89: throw xBoundary();

90: return pType[0];

91: }

92:

93: const intArray::operator[] (int offSet) const

94: {

95: int size = GetitsSize();

96: if (offSet = 0 offSet GetitsSize())

97: return pType[offSet];

98: throw xBoundary();

99: return pType[0];

100: }

101:

102: int main()

103: {

104:

105: try

106: {

107: Array intArray(9);

108: for (int j = 0; j 100; j++)

109: {

110: intArray[j] = j;

111: cout

112: }

113: }

114: catch (Array::xBoundary)

115: {

116: cout

117: }

118: catch(Array::xZero theException)

119: {

120: cout

121: cout

122: }

123: catch (Array:;xTooBig theException)

124: {

125: cout

126: cout

127: }

128: catch (Array;:xTooSmall theException)

129: {

130: cout

131: cout

132: }

133: catch (…)

134: {

135: cout

136: }

137: cout

138: return 0;

139: }

Итог:

This array is too small…

Received 9

Done.

Анализ: Объявление класса xSize было поменяно так, дабы включить в него переменную-член itsSize (строкаЗЗ) и функцию-член GetSize() (строчок 31). Помимо этого, был добавлен конструктор, что принимает целое число и инициализирует переменную-член, как продемонстрировано в строчке 29.

Производные классы объявляют конструктор, что только инициализирует базисный класс. Наряду с этим никакие другие функции заявлены не были (частично из экономии места в листинге).

Операторы catch в строчках 114-136 поменяны так, дабы создавать именованный объект исключения (thoException), что употребляется в теле блока catch для доступа к данным, сохраняемым в переменной-участнике itsSize.

Примечание: При работе с исключениями направляться не забывать об их сути: в случае, если уж оно появилось, значит, что-то не в порядке с распределением ресурсов, и обработку этого исключения необходимо записать так, дабы снова не создать ту же проблему. Следовательно, если вы создаете исключение OutOfMemory, то не следует а конструкторе этого класса пробовать выделить память для какого-либо объекта.

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

Листинг 20.5. Передача доводов как ссылок u применение виртуальных функций в классах исключений

1: #include

2:

3: const int DefaultSize = 10;

4:

5: class Array

6: {

7: public:

8: // конструкторы

9: Array(int itsSize = DefaultSize);

10: Array(const Array rhs);

11: ~Array() { delete [] pType;}

12:

13: // операторы

14: Array operator=(const Array);

15: int operator[](int offSet);

16: const int operator[](int offSet) const;

17:

18: // способы доступа

19: int GetitsSize() const { return itsSize; }

20:

21: // функция-приятель

22: friend ostream operator

23: (ostream, const Array);

24:

25: // определение классов исключений

26: class xBoundary { };

27: class xSize

28: {

29: public:

30: xSize(int size):itsSize(size) { }

31: ~xSize(){ }

32: virtual int GetSize() { return itsSize; }

33: virtual void PrintError()

34: {

35: cout

36: cout

37: }

38: protected:

39: int itsSize;

40: };

41:

42: class xTooBig : public xSize

43: {

44: public:

45: xTooBig(int size):xSize(size){ }

46: virtual void PrintError()

47: {

48: cout

49: cout

50: }

51: };

52:

53: class xTooSmall : public xSize

54: {

55: public:

56: xTooSmall(int size):xSize(size){ }

57: virtual void PrintError()

58: {

59: cout

60: cout

61: }

62: };

63:

64: class xZero : public xTooSmall

65: {

66: public:

67: xZero(int size):xTooSmall(size){ }

68: virtual void PrintError()

69: {

70: cout

71: cout

72: }

73: };

74:

75: class xNegative : public xSize

76: {

77: public:

78: xNegative(int size):xSize(size){ }

79: virtual void PrintError()

80: {

81: cout

82: cout

83: }

84: };

85:

86: private:

87: int *pType;

88: int itsSize;

89: };

90:

91: Array::Array(int size):

92: itsSize(size)

93: {

94: if (size == 0)

95: throw xZero(size);

96: if (size 30000)

97: throw xTooBig(size);

98: if (size

99: throw xNegative(size);

100: if (size 10)

101: throw xTooSmall(size);

102:

103: pType = new int[size];

104: for (int i = 0: i

105: pType[i] = 0;

106: }

107:

108: int Array::operator[] (int offSet)

109: {

110: int size = GetitsSize();

111: if (offSet = 0 offSet GetitsSize())

112: return pType[offSet];

113: throw xBoundary();

114: return pType[0];

115: }

116:

117: const int Array::operator[] (int offSet) const

118: {

119: int size = GetitsSize();

120: if (offSet = 0 offSet GetitsSize())

121: return pType[offSet];

122: throw xBoundary();

123: return pType[0];

124: }

125:

126: int main()

127: {

128:

129: try

130: {

131: Array intArray(9);

132: for (int j = 0: j 100; j++)

133: {

134: intArray[j] — j;

135: cout

136: }

137: }

138: catch (Array::xBoundary)

139: {

140: cout

141: }

142: catch (Array;:xSize theExoeption)

143: {

144: theException.PrintError();

145: }

146: catch (…)

147: {

148: cout

149: }

150: cout

151: return 0;

152: }

Итог:

Too small! Received: 9

Done.

Анализ: В листинге 20.5 продемонстрировано объявление виртуального способа PrintError() в классе xSize, что выводит сообщения об неточностях и подлинный размер класса. Данный способ замешается в каждом производном классе исключения.

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

Исключения и шаблоны

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

Листинг 20.6. Применение исключений с шаблонами

1: #include

2:

3: const int DefaultSize = 10;

4: class xBoundary { } ;

5:

6: template

7: class Array

8: {

9: public:

10: // конструкторы

11: Array(int itsSize = DefaultSize);

12: Array(const Array rhs);

13: ~Array() { delete [] pType;}

14:

15: // операторы

16: Array operator=(const Array);

17: T operator[](int offSet);

18: const T operator[](int offSet) const;

19:

20: // способы доступа

21: int GetitsSize() const { return itsSize; }

22:

23: // функция-приятель

24: friend ostream operator

25:

26: // определение классов исключений

27:

28: class направляться { };

29:

30: private:

31: int *pType;

32: int itsSize;

33: };

34:

35: template

36: Array::Array(int size):

37: itsSize(size)

38: {

39: if (size 30000)

40: throw xSize();

41: рТуре = new T[size];

42: for (int i = 0; i

43: pType[i] = 0;

44: }

45:

46: template

47: Array Array::operator=(const Array rhs)

48: {

49: if (this == rhs)

50: return *this;

51: delete [] рТуре;

52: itsSize = rhs.GetitsSize();

53: рТуре = new T[itsSize];

54: for (int i = 0; i

55: pType[i] = rhs[i];

56: }

57: template

58: Array::Array(const Array rhs)

59: {

60: itsSize = rhs.GetitsSize();

61: рТуре = new T[itsSize];

62: for (int i = 0; i

63: pType[i] = rhs[i];

64: }

65:

66: template

67: T Array::operator[](int offSet)

68: {

69: int size = GetitsSize();

70: if (offSet = 0 offSet GetitsSize())

71: return pType[offSet];

72: throw xBoundary():

73: return pType[0];

74: }

75:

76: template

77: const T Array::operator[](int offSet) const

78: {

79: int mysize = GetitsSize();

80: if (offSet = 0 offSet GetitsSize())

81: return pType[offSet];

82: throw xBoundary();

83: }

84:

85: template

86: ostream operator

87: {

88: for (int i = 0; i

89: output

90: return output;

91: }

92:

93:

94: int main()

95: {

96:

97: try

98: {

99: Array intArray(9);

100: for (int j = 0; j 100; j++)

101: {

102: intArray[j] = j;

103: cout

104: }

105: }

106: catch (xBoundary)

107: {

108: cout

109: }

110: catch (Array::xSize)

111: {

112: cout

113: }

114:

115: cout

116: return 0;

117: }

Итог:

You asked for an array of zero objects!

Done

Анализ: Первое исключение, xBoundary, заявлено вне определения шаблона в строчке 4; второе исключение, xSize, — в определения шаблона в строчке 28. Исключение xBoundary не связано с классом шаблона, но его возможно применять равно как и каждый класс. Исключение xSize связано с шаблоном и должно вызываться для экземпляра класса Array. Обратите внимание на отличие в синтаксисе двух операторов catch. Строчок 106 содержит выражение catch (xBoundary), а строчок 110 — выражение catch (Array::xSize). Второй вариант связан с обращением к исключению экземпляра целочисленного массива.

Исключения без неточностей

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

Одновременно с этим другие уверены в том, что исключения предоставляют действенный метод возврата через пара уровней вызовов функций, не подвергаясь наряду с этим опасности утечки памяти. Значительно чаще приводится следующий пример. Пользователь формирует запрос на некую операцию в среде GU1 (графический интерфейс пользователя). Часть кода, которая перехватывает данный запрос, обязана привести к функции-член менеджера диалоговых окон, которая, со своей стороны, приводит к коду, обрабатывающий данный запрос. Данный код вызывает второй код, что решает, какое диалоговое окно применять, и, со своей стороны, приводит к коду, дабы отобразить на экране это диалоговое окно. И сейчас уже данный код наконец-то вызывает второй код, что обрабатывает эти, вводимые пользователем. В случае, если пользователь щелкнет на кнопке Cancel (Отменить), код обязан возвратиться к самому первому вызывающему способу, где обрабатывался начальный запрос.

Один подход к ответу данной неприятности пребывает в том, дабы поместить блок try сходу за тем блоком программы, где формируется исходный запрос, и перехватывать объект исключения CancelDialog, что генерируется обработчиком сообщений для кнопки Cancel. Это безопасно и действенно, не смотря на то, что щелчок на кнопке Cancel по сути собственной не относится к необыкновенной обстановке.

Дабы решить, как правомочно такое применение исключений, постарайтесь ответить на следующие вопросы: станет ли в следствии программа несложнее либо, напротив, тяжелее для понимания; вправду ли уменьшится утечки возникновения памяти и риск ошибок; тяжелее либо несложнее сейчас станет помощь таковой программы? Непременно, объективно ответить на эти вопросы сложно: очень многое зависит от субъективных взглядов и привычек программиста, почему, кстати, и появляются споры около этих вопросов.

Неточности и отладка программы

Практически все современные среды разработки содержат один либо пара встроенных действенных отладчиков. Главная мысль применения отладчика такова: отладчик загружает и делает исходный код программы в режиме, эргономичном для отслеживания исполнения отдельных выявления ошибок и строк программы.

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

Полноэкранные символьные отладчики превосходно справляются с данной непростой работой. По окончании загрузки отладчик считывает целый исходный код программы и отображает его в окне. Отладчик разрешает проходить в походовом режиме через все строки программы в порядке их исполнения.

При работе с большинством отладчиков возможно переключаться между выводом и исходным кодом на экран, дабы видеть результаты исполнения каждой команды. Нужной кроме этого есть возможность определения текущего значения любой переменной, а также переменных-участников классов и значений в ячейках области динамического обмена, на каковые ссылаются указатели программы, и просмотр сложных структур данных. Отладчики предоставляют последовательность утилит, разрешающих устанавливать в коде программы точки останова, выводить контрольные значения переменных, изучить особенности распределения памяти и просматривать код ассемблера.

Точка останова

Точки останова — это команды, предназначенные для отладчика и означающие, что программа обязана остановиться перед исполнением указанной строки. Это средство разрешает экономить время при отладке, делая программу как и всегда до того места, где установлена точка останова. По окончании остановки исполнения программы возможно проанализировать текущие значения переменных либо продолжить работу программы в походовом режиме.

Анализ значений переменных

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

Изучение памяти

Иногда принципиально важно просматривать настоящие значения, содержащиеся в памяти. Современные отладчики смогут отображать эти значения в понятном для пользователя виде, т.е. строчки отображаются как знаки, а числовые значения — как десятичные цифры, а не в бинарном коде. Современные отладчики C++ смогут кроме того показывать целые классы с текущими значениями всех переменных-участников, включая указатель this.

Код ассемблера

Не смотря на то, что чтения исходного кода время от времени не редкость достаточно для обнаружения неточности, однако возможно указать отладчику на отображение настоящего кода ассемблера, сгенерированного для каждой строки исходного кода. Вы имеете возможность просмотреть флагов регистраторов и значения памяти и при жажде так углубиться в дебри машинного кода, как необходимо.

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

Резюме

Сейчас вы выяснили, как создавать и применять исключения, т.е. объекты, каковые смогут быть созданы в тех местах программы, где исполняемый код не имеет возможности обработать неточность либо другую необыкновенную обстановку, появившуюся на протяжении исполнения программы. Другие части программы, расположенные выше в стеке вызовов, делают блоки catch, каковые перехватывают исключение и отвечают на появившуюся необыкновенную обстановку соответствующим образом.

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

Возможно создать конструкции из нескольких блоков catch, но направляться учитывать, что, когда исключение будет перехвачено отдельным оператором catch, оно не будет передаваться последующим блокам catch. Крайне важно верно упорядочить блоки catch, дабы своеобразные блоки находились выше более неспециализированных блоков.

На этом занятии кроме этого рассматривались кое-какие основные принципы работы символьных отладчиков, включая применение таких средств, как точки останова, анализ значений переменных и т.д. Эти средства разрешают выполнить останов программы в той части, которая приводит к появлению неточности, и просмотреть значения переменных на протяжении программы.

ответы и Вопросы

Для чего тратить время на программирование исключений? Не лучше ли ликвидировать неточности по мере их происхождения?

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

Для чего создавать исключения как объекты? Не несложнее ли записать код устранения неточности?

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

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

Непременно, и многие программисты на C++ применяют исключения конкретно в этих целях. Но направляться не забывать, что прохождение исключения по стеку вызовов может оказаться не таким уж надёжным. Так, в случае, если объект был создан в области динамического обмена, а позже удален в стеке вызовов, это может привести к утечке памяти. Но, при использовании и тщательном анализе программы современного компилятора эту проблему возможно предотвратить.

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

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

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

Для чего применять утилиту отладки, в случае, если те же функции возможно осуществлять прямо на протяжении компиляции посредством объекта cout и условного выражения #ifdef debug?

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

Коллоквиум

В этом разделе предлагаются вопросы для укрепления и самоконтроля взятых знаний, и последовательность упражнений, каковые окажут помощь закрепить ваши практические навыки. Постарайтесь самостоятельно ответить на вопросы теста и выполнить задания, а позже сверьте полученные результаты с ответами в приложении Г. Не приступайте к изучению материала следующей главы, в случае, если для вас остались неясными хотя бы кое-какие из предложенных ниже вопросов.

Контрольные вопросы

1. Что такое исключение?

2. Для чего нужен блок try?

3. Для чего употребляется оператор catch?

4. Какую данные может содержать исключение?

5. В то время, когда создается объект исключения?

6. направляться ли передавать исключения как значения либо как ссылки?

7. Будет ли оператор catch перехватывать производные исключения, если он настроен на базисный класс исключения?

8. В случае, если употребляются два оператора catch, один из которых настроен на базисное сообщение, а второй — на производное, то в каком порядке их направляться расположить?

9. Что свидетельствует оператор catch(…)?

10. Что такое точка останова?

Упражнения

1. Запишите блок try и оператор catch для обработки и отслеживания несложного исключения.

2. Добавьте в исключение, полученное в упражнении 1, переменную-метод и член доступа и применяйте их в блоке оператора catch.

3. Унаследуйте новое исключение от исключения, взятого в упражнении 2. Измените блок оператора catch так, дабы в нем происходила обработка как производного, так и базисного исключений.

4. Измените код упражнения 3, чтобы получить трехуровневый вызов функции.

5. Жучки: что не верно в следующем коде?

#include string //класс строчков

сlass xOutOfMemory

{

public:

xOutOfMemory(){ theMsg = new сhar[20];

strcpy(theMsg, trror in momory);}

~xOutOfMemory(){ delete [] theMsg;

cout

char * Message() { return theMsg; }

private:

char theMsg;

};

main()

{

try

{

char * var = new char;

if ( var == 0 )

{

xOutOfMemory * px = new xOutOfMemory;

throw px;

}

}

catch( xOutOfMemory * theException )

{

cout

delete theException;

}

return 0;

}

6. Этот пример содержит потенциальную неточность, подобную появляющейся при попытке выделить память для показа сообщения об неточности при обнаружении дефицита свободной памяти. Вы имеете возможность протестировать эту программу, поменяв строчок if (var == 0) на if (1), которая приведёт к созданию исключения.

Сутки 21-й. Что дальше

Примите отечественные поздравления! Вы практически завершили изучение полного трехнедельного интенсивного курса введения в C++. Сейчас у вас должно быть ясное познание языка C++, но в современном программировании постоянно найдутся еще не изученные области. В данной главе будут рассмотрены кое-какие опущенные выше подробности, а после этого намечен курс для предстоящего освоения C++.

Большинство кода файлов источника представлена командами на языке C++. Компилятор превращает данный код в программу на машинном языке. Но перед запуском компилятора запускается препроцессор, благодаря чему возможно воспользоваться возможностями условной компиляции. Итак, сейчас вы определите:

• Что представляет собой условная компиляция и как с ней обращаться

• Как записывать макросы препроцессора

• Как применять препроцессор для обнаружения неточностей

• Как руководить значениями отдельных битов и применять их в качестве знамён

• Какие конкретно шаги направляться предпринять для предстоящего действенного изучения C++

компилятор и Процессор

При каждом запуске компилятора сперва запускается препроцессор, что ищет команды препроцессора, начинающиеся с знака фунта (#). При исполнении любой из этих команд в текст исходного кода вносятся кое-какие трансформации, в следствии чего создается новый файл исходного кода. Данный новый файл есть временным, и вы в большинстве случаев его не видите, но имеете возможность дать приказ компилятору сохранить его для просмотра и использования.

Компилятор просматривает не исходный файл источника, а итог работы препроцессора и компилирует его в исполняемый файл программы. Вам уже приходилось видеться с директивой препроцессора #include: она предписывает отыскать файл, имя которого следует за ней, и засунуть текст этого файла по месту вызова. Данный эффект подобен следующему: вы абсолютно вводите этот файл прямо в собственную исходную программу, причем к тому времени, в то время, когда компилятор возьмёт исходный код, файл будет уже на месте.

КАК КОЛИЧЕСТВО БУКВ В ИМЕНИ ВЛИЯЕТ НА СУДЬБУ


Интересные записи:

Понравилась статья? Поделиться с друзьями: