Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции C++.doc
Скачиваний:
7
Добавлен:
01.05.2025
Размер:
1.44 Mб
Скачать

5.11 Раннее и позднее связывание

Имеются два термина, часто используемые, когда речь заходит об объектно-ориентированных языках программирования: раннее и позднее связывание. По отношению к C++ эти термины соответствуют событиям, которые возникают на этапе компиляции и на этапе исполнения про­граммы соответственно.

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

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

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

Глава 6. Подсистема динамического выделения памяти

6.1 Введение в обработку исключений

Обработка исключений (exception handling) позволяет упорядочить обработку ошибок времени исполнения. Используя обработку исключений C++, программа может автоматически вызвать функцию-обработчик ошибок тогда, когда такая ошибка возникает. Принципиальным достоин­ством обработки исключений служит то, что она позволяет автоматизировать большую часть кода для обработки ошибок, для чего раньше требовалось ручное кодирование.

Обработка исключений в C++ использует три ключевых слова: try, catch и throw. Те инструкции программы, где ожидается возможность появления исключительных ситуаций, нужно помещать в бло­ке try. Если в блоке try возникает исключение, т. е. ошибка, то генерируется исключение. Исклю­чение перехватывается в блоке catch и обрабатывается. Ниже это общее описание будет рас­смотрено более подробно.

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

try

{

// блок try

}

catch(тип1 аргумент1)

{

// блок catch

}

catch(тип2 аргумент2)

{

// блок catch

}

catch(тип3 аргумент3)

{

// блок catch

}

catch(тип4 аргумент4)

{

// блок catch

}

Размеры блока try могут изменяться в больших пределах. Например, блок try может содержать несколько инструкций какой-либо функции, либо же, напротив, включать в себя весь код функ­ции main(), так что вся программа будет охвачена обработкой исключений.

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

Общая форма записи инструкции throw имеет вид:

throw исключение;

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

Если генерируется исключение, для которого отсутствует подходящая инструкция catch, может произойти аварийное завершение программы. При генерации необработанного исключения вызывается функция terminate(). По умолчанию terminate() вызывает функцию abort(), завершаю­щую выполнение программы. Однако можно задать свою собственную обработку, используя фун­кцию set_terminate(). Ниже представлен пример, иллюстрирующий способ обработки исключений в C++:

#include <iostream.h>

int main()

{

cout << "Start\n";

try // начало блока try

{

cout << "Inside try block\n";

throw 100; // генерация ошибки

cout << "This will not execute";

}

catch(int i) // перехват ошибки

{

cout << "Caught an exception -- value is: << i << "\n";

}

cout << "End";

return 0;

}

Программа выведет на экран следующий текст:

Start

Inside try block

Caught an exception -- value is: 100

End

Рассмотрим внимательнее эту программу. Как можно видеть, блок try содержит три инструкции. За ним следует инструкция catch(int i), обрабатывающая исключения целого типа. В блоке try будут выполняться только две инструкции: первая и вторая — throw. Как только исключение было сгенерировано, управление передается блоку catch, а блок try прекращает свое исполнение. Таким образом, инст­рукция после инструкции throw никогда не выполняется.

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

Рассмотрим еще один пример с исключением строкового типа:

#include <iostream.h>

int main()

{

char *buf;

try

{

buf = new char[512];

if(!buf) throw "Memory allocation failure!";

}

catch(char *string)

{

cout << "Caught exception: " << string << "\n";

}

// ...

return 0;

}

При генерации исключения оно перехватывается блоком catch, а строка "Memory allocation failure!" передается блоку catch в качестве параметра.