С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 13
Текст из файла (страница 13)
Так, параметр конструктораимеет по умолчанию значение 0:explicit Example2 (elemType val=0) : _val(val) {};Однако не все типы могут быть инициализированы нулем (например, тип string),поэтому определение объектаExample2<string> exs("Walden");является правильным, аExample2<string> exs2;приведет к синтаксической ошибке4. Также ошибочным будет вызов функции min(), еслидля данного типа не определена операция меньше. С++ не позволяет задать ограничениядля типов, подставляемых в шаблоны. Как вы думаете, было бы полезным иметь такуювозможность? Если да, попробуйте придумать синтаксис задания ограничений иперепишите в нем определение класса Example2. Если нет, поясните почему.Упражнение 2.17Как было показано в предыдущем упражнении, попытка использовать шаблон Example2с типом, для которого не определена операция меньше, приведет к синтаксическойошибке.
Однако ошибка проявится только тогда, когда в тексте компилируемойпрограммы действительно встретится вызов функции min(), в противном случаекомпиляция пройдет успешно. Как вы считаете, оправдано ли такое поведение? Не лучшели предупредить об ошибке сразу, при обработке описания шаблона? Поясните своемнение.4 Вот как выглядит общее решение этой проблемы:Example2( elemType nval = elemType() ) " _val( nval ) {}60С++ для начинающих2.6. Использование исключенийИсключениями называют аномальные ситуации, возникающие во время исполненияпрограммы: невозможность открыть нужный файл или получить необходимое количествопамяти, использование выходящего за границы индекса для какого-либо массива.Обработка такого рода исключений, как правило, плохо интегрируется в основнойалгоритм программы, и программисты вынуждены изобретать разные способыкорректной обработки исключения, стараясь в то же время не слишком усложнитьпрограмму добавлением всевозможных проверок и дополнительных ветвей алгоритма.С++ предоставляет стандартный способ реакции на исключения.
Благодаря вынесению вотдельную часть программы кода, ответственного за проверку и обработку ошибок,значительно облегчается восприятие текста программы и сокращается ее размер. Единыйсинтаксис и стиль обработки исключений можно, тем не менее, приспособить к самымразнообразным нуждам и запросам.Механизм исключений делится на две основные части:точка программы, в которой произошло исключение. Определение того факта, что привыполнении возникла какая-либо ошибка, влечет за собой возбуждение исключения. Дляэтого в С++ предусмотрен специальный оператор throw.
Возбуждение исключения вif ( !infile ) {string errMsg("Невозможно открыть файл: ");errMsg += fileName;throw errMsg;случае невозможности открыть некоторый файл выглядит следующим образом:}Место программы, в котором исключение обрабатывается. При возбужденииисключения нормальное выполнение программы приостанавливается и управлениепередается обработчику исключения. Поиск нужного обработчика часто включает в себяраскрутку так называемого стека вызовов программы. После обработки исключениявыполнение программы возобновляется, но не с того места, где произошло исключение, ас точки, следующей за обработчиком.
Для определения обработчика исключения в С++используется ключевое слово catch. Вот как может выглядеть обработчик для примера изпредыдущего абзаца:catch (string exceptionMsg) {log_message (exceptionMsg);return false;}Каждый catch-обработчик ассоциирован с исключениями, возникающими в блокеоператоров, который непосредственно предшествует обработчику и помечен ключевымсловом try. Одному try-блоку могут соответствовать несколько catch-предложений,int* stats (const int *ia, int size)каждое из которых относится к определенному виду исключений.
Приведем пример:{int *pstats = new int [4];61С++ для начинающихtry {pstats[0] = sum_it (ia,size);pstats[1] = min_val (ia,size);pstats[2] = max_val (ia,size);}catch (string exceptionMsg) {// код обработчика}catch (const statsException &statsExcp) {// код обработчика}pstats [3] = pstats[0] / size;do_something (pstats);return pstats;}В данном примере в теле функции stats() три оператора заключены в try-блок, ачетыре – нет. Из этих четырех операторов два способны возбудить исключения.1) int *pstats = new int [4];Выполнение оператора new может окончиться неудачей.
Стандартная библиотека С++предусматривает возбуждение исключения bad_alloc в случае невозможности выделитьнужное количество памяти. Поскольку в примере не предусмотрен обработчикисключения bad_alloc, при его возбуждении выполнение программы закончитсяаварийно.2) do_something (pstats);Мы не знаем реализации функции do_something(). Любая инструкция этой функции,или функции, вызванной из этой функции, или функции, вызванной из функции,вызванной этой функцией, и так далее, потенциально может возбудить исключение. Еслив реализации функции do_something и вызываемых из нее предусмотрен обработчиктакого исключения, то выполнение stats() продолжится обычным образом. Если жетакого обработчика нет, выполнение программы аварийно завершится.Необходимо заметить, что, хотя операторpstats [3] = pstats[0] / size;может привести к делению на ноль, в стандартной библиотеке не предусмотрен такой типисключения.Обратимся теперь к инструкциям, объединенным в try-блок.
Если в одной из вызываемыхв этом блоке функций – sum_it(), min_val() или max_val() –произойдетисключение, управление будет передано на обработчик, следующий за try-блоком иперехватывающий именно это исключение. Ни инструкция, возбудившая исключение, ниследующие за ней инструкции в try-блоке выполнены не будут. Представим себе, чтопри вызове функции sum_it() возбуждено исключение:throw string ("Ошибка: adump27832");Выполнение функции sum_it() прервется, операторы, следующие в try-блоке завызовом этой функции, также не будут выполнены, и pstats[0] не будетинициализирована. Вместо этого возбуждается исключительное состояние и исследуютсядва catch-обработчика. В нашем случае выполняется catch с параметром типа string:62С++ для начинающихcatch (string exceptionMsg) {// код обработчика}После выполнения управление будет передано инструкции, следующей за последнимcatch-обработчиком, относящимся к данному try-блоку.
В нашем случае этоpstats [3] = pstats[0] / size;(Конечно, обработчик сам может возбуждать исключения, в том числе – того же типа. Втакой ситуации будет продолжено выполнение catch-предложений, определенных впрограмме, вызвавшей функцию stats().)catch (string exceptionMsg) {Вот пример:// код обработчикаcerr << "stats(): исключение: "<< exceptionMsg<< endl;delete [] pstats;return 0;}В таком случае выполнение вернется в функцию, вызвавшую stats(). Будем считать,что разработчик программы предусмотрел проверку возвращаемого функцией stats()значения и корректную реакцию на нулевое значение.Функция stats() умеет реагировать на два типа исключений: string иstatsException.
Исключение любого другого типа игнорируется, и управлениепередается в вызвавшую функцию, а если и в ней не найдется обработчика, – то вфункцию более высокого уровня, и так до функции main().При отсутствии обработчикаи там, программа аварийно завершится.Возможно задание специального обработчика, который реагирует на любой типcatch (...) {// обрабатывает любое исключение,// однако ему недоступен объект, переданный// в обработчик в инструкции throwисключения. Синтаксис его таков:}(Детально обработка исключительных ситуаций рассматривается в главах 11 и 19.)Упражнение 2.18Какие ошибочные ситуации могут возникнуть во время выполнения следующей функции:int *alloc_and_init (string file_name){ifstream infile (file_name)63С++ для начинающихint elem_cnt;infile >> elem_cnt;int *pi = allocate_array(elem_cnt);int elem;int index=0;while (cin >> elem)pi[index++] = elem;sort_array(pi,elem_cnt);register_data(pi);return pi;}Упражнение 2.19В предыдущем примере вызываемые функции allocate_array(), sort_array() иregister_data() могут возбуждать исключения типов noMem, int и stringсоответственно.
Перепишите функцию alloc_and_init(), вставив соответствующиеблоки try и catch для обработки этих исключений. Пусть обработчики просто выводят вcerr сообщение об ошибке.Упражнение 2.20Усовершенствуйте функцию alloc_and_init() так, чтобы она сама возбуждалаисключение в случае возникновения всех возможных ошибок (это могут бытьисключения, относящиеся к вызываемым функциям allocate_array(), sort_array()и register_data() и какими-то еще операторами внутри функции alloc_and_init()).Пусть это исключение имеет тип string и строка, передаваемая обработчику, содержитописание ошибки.2.7.
Использование пространства именПредположим, что мы хотим предоставить в общее пользование наш класс Array,разработанный в предыдущих примерах. Однако не мы одни занимались этойпроблемой; возможно, кем-то где-то, скажем, в одном из подразделений компании Intelбыл создан одноименный класс. Из-за того что имена этих классов совпадают,потенциальные пользователи не могут задействовать оба класса одновременно, онидолжны выбрать один из них. Эта проблема решается добавлением к имени классанекоторой строки, идентифицирующей его разработчиков, скажем,class Cplusplus_Primer_Third_Edition_Array { ... };Конечно, это тоже не гарантирует уникальность имени, но с большой вероятностьюизбавит пользователя от данной проблемы.
Как, однако, неудобно пользоваться стольдлинными именами!Стандарт С++ предлагает для решения проблемы совпадения имен механизм,называемый пространством имен. Каждый производитель программного обеспеченияможет заключить свои классы, функции и другие объекты в свое собственноепространство имен. Вот как выглядит, например, объявление нашего класса Array:64С++ для начинающихnamespace Cplusplus_Primer_3E {template <class elemType> class Array { ...
};}Ключевое слово namespace задает пространство имен, определяющее видимость нашегокласса и названное в данном случае Cplusplus_Primer_3E. Предположим, что у нас естьклассы от других разработчиков, помещенные в другие пространства имен:namespace IBM_Canada_Laboratory {template <class elemType> class Array { ... };class Matrix { ... };}namespace Disney_Feature_Animation {class Point { ... };template <class elemType> class Array { ...
};}По умолчанию в программе видны объекты, объявленные без явного указанияпространства имен; они относятся к глобальному пространству имен. Для того чтобыобратиться к объекту из другого пространства, нужно использовать егоквалифицированное имя, которое состоит из идентификатора пространства имен иидентификатора объекта, разделенных оператором разрешения области видимости (::).Cplusplus_Primer_3E::Array<string> text;Вот как выглядят обращения к объектам приведенных выше примеров:IBM_Canada_Laboratory::Matrix mat;Disney_Feature_Animation::Point origin(5000,5000);Для удобства использования можно назначать псевдонимы пространствам имен.Псевдоним выбирают коротким и легким для запоминания. Например:// псевдонимыnamespace LIB = IBM_Canada_Laboratory;namespace DFA = Disney_Feature_Animation;int main(){LIB::Array<int> ia(1024);}Псевдонимы употребляются и для того, чтобы скрыть использование пространств имен.Заменив псевдоним, мы можем сменить набор задействованных функций и классов,причем во всем остальном код программы останется таким же.
Исправив только однустрочку в приведенном выше примере, мы получим определение уже совсем другогомассива:namespace LIB = Cplusplus_Primer_3E;int main(){LIB::Array<int> ia(1024);}65С++ для начинающихКонечно, чтобы это стало возможным, необходимо точное совпадение интерфейсовклассов и функций, объявленных в этих пространствах имен. Представим, что классArray из Disney_Feature_Animation не имеет конструктора с одним параметром –размером. Тогда следующий код вызовет ошибку:namespace LIB = Disney_Feature_Animation;int main(){LIB::Array<int> ia(1024);}Еще более удобным является способ использования простого, неквалифицированногоимени для обращения к объектам, определенным в некотором пространстве имен.