С++ для начинающих


Try-блок


В нашей программе тестируется определенный в предыдущем разделе класс iStack и его функции-члены pop() и push(). Выполняется 50 итераций цикла for. На каждой итерации в стек помещается значение, кратное 3: 3, 6, 9 и т.д. Если значение кратно 4 (4, 8, 12...), то выводится текущее содержимое стека, а если кратно 10 (10, 20, 30...), то с вершины снимается один элемент, после чего содержимое стека выводится снова. Как нужно изменить функцию main(), чтобы она обрабатывала исключения, возбуждаемые функциями-членами класса iStack?

#include <iostream>

#include "iStack.h"

int main() {

   iStack stack( 32 );

   stack.display();

   for ( int ix = 1; ix < 51; ++ix )

   {

             if ( ix % 3 == 0 )

          stack.push( ix );

             if ( ix % 4 == 0 )

                stack.display();

             if ( ix % 10  == 0 ) {



                int dummy;

                stack.pop( dummy );

                stack.display();

             }

   }

   return 0;

}

Инструкции, которые могут возбуждать исключения, должны быть заключены в try-блок. Такой блок начинается с ключевого слова try, за которым идет последовательность инструкций, заключенная в фигурные скобки, а после этого – список обработчиков, называемых catch-предложениями. Try-блок группирует инструкции программы и ассоциирует с ними обработчики исключений. Куда нужно поместить try-блоки в функции main(), чтобы были обработаны исключения popOnEmpty и pushOnFull?

for ( int ix = 1; ix < 51; ++ix ) {

   try {   // try-блок для исключений pushOnFull

            if ( ix % 3 == 0 )

         stack.push( ix );

   }

   catch ( pusOnFull ) { ... }

   if ( ix % 4 == 0 )

      stack.display();

   try {   // try-блок для исключений popOnEmpty

            if ( ix % 10  == 0 ) {

               int dummy;

               stack.pop( dummy );

               stack.display();

            }

   }

   catch ( popOnEmpty ) { ... }

}

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


try {

   for ( int ix = 1; ix < 51; ++ix )

   {

      if ( ix % 3 == 0 )

         stack.push( ix );

      if ( ix % 4 == 0 )

               stack.display();

      if ( ix % 10  == 0 ) {

               int dummy;

               stack.pop( dummy );

               stack.display();

      }

   }

}

catch ( pushOnFull ) { ... }

catch ( popOnEmpty ) { ... }

С try-блоком ассоциированы два catch-предложения, которые могут обработать исключения pushOnFull и popOnEmpty, возбуждаемые функциями-членами push() и pop() внутри этого блока. Каждый catch-обработчик определяет тип “своего” исключения. Код для обработки исключения помещается внутрь составной инструкции (между фигурными скобками), которая является частью catch-обработчика. (Подробнее catch-предложения мы рассмотрим в следующем разделе.)

Исполнение программы может пойти по одному из следующих путей:

  • если исключение не возбуждено, то выполняется код внутри try-блока, а ассоциированные с ним обработчики игнорируются. Функция main() возвращает 0;


  • если функция-член push(), вызванная из первой инструкции if внутри цикла for, возбуждает исключение, то вторая и третья инструкции if игнорируются, управление покидает цикл for и try-блок, и выполняется обработчик исключений типа pushOnFull;


  • если функция-член pop(), вызванная из третьей инструкции if внутри цикла for, возбуждает исключение, то вызов display() игнорируется, управление покидает цикл for и try-блок, и выполняется обработчик исключений типа popOnEmpty.


  • Когда возбуждается исключение, пропускаются все инструкции, следующие за той, где оно было возбуждено. Исполнение программы возобновляется в catch-обработчике этого исключения. Если такого обработчика не существует, то управление передается в функцию terminate(), определенную в стандартной библиотеке C++.

    Try-блок может содержать любую инструкцию языка C++: как выражения, так и объявления. Он вводит локальную область видимости, так что объявленные внутри него переменные недоступны вне этого блока, в том числе и в catch-обработчиках. Например, функцию main() можно переписать так, что объявление переменной stack окажется в try-блоке. В таком случае обращаться к этой переменной в catch-обработчиках нельзя:



    int main() {

       try {

          iStack stack( 32 );    // правильно: объявление внутри try-блока

          stack.display();

          for ( int ix = 1; ix < 51; ++ix )

          {

              // то же, что и раньше

          }

       }

       catch ( pushOnFull ) {

          // здесь к переменной stack обращаться нельзя

       }

       catch ( popOnEmpty ) {

          // здесь к переменной stack обращаться нельзя

       }

       // и здесь к переменной stack обращаться нельзя

       return 0;

    }

    Можно объявить функцию так, что все ее тело будет заключено в try-блок. При этом не обязательно помещать try-блок внутрь определения функции, удобнее заключить ее тело в функциональный try-блок. Такая организация поддерживает наиболее чистое разделение кода для нормальной обработки и кода для обработки исключений. Например:

    int main()

       try {

          iStack stack( 32 );    // правильно: объявление внутри try-блока

          stack.display();

          for ( int ix = 1; ix < 51; ++ix )

          {

              // то же, что и раньше

          }

          return 0;

       }

       catch ( pushOnFull ) {

          // здесь к переменной stack обращаться нельзя

       }

       catch ( popOnEmpty ) {

          // здесь к переменной stack обращаться нельзя

       }

    Обратите внимание, что ключевое слово try находится перед фигурной скобкой, открывающей тело функции, а catch-обработчики перечислены после закрывающей его скобки. Как видим, код, осуществляющий нормальную обработку, находится внутри тела функции и четко отделен от кода для обработки исключений. Однако к переменным, объявленным в main(), нельзя обратиться из обработчиков исключений.

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

    Упражнение 11.3

    Напишите программу, которая определяет объект IntArray (тип класса IntArray рассматривался в разделе 2.3) и выполняет описанные ниже действия.

    Пусть есть три файла, содержащие целые числа.

    1.      Прочитать первый файл и поместить в объект IntArray первое, третье, пятое, ..., n-ое значение (где n нечетно). Затем вывести содержимое объекта IntArray.

    2.      Прочитать второй файл и поместить в объект IntArray пятое, десятое, ..., n-ое значение (где n кратно 5). Вывести содержимое объекта.

    3.      Прочитать третий файл и поместить в объект IntArray второе, четвертое, ..., n-ое значение (где n четно). Вывести содержимое объекта.

    Воспользуйтесь оператором operator[]() класса IntArray, определенным в упражнении 11.2, для сохранения и получения значений из объекта IntArray. Так как operator[]() может возбуждать исключения, обработайте их, поместив необходимое количество try-блоков и catch-обработчиков. Объясните, почему вы разместили try-блоки именно так, а не иначе.


    Содержание раздела