А.В. Столяров - Введение в язык Си++ (1114949), страница 24
Текст из файла (страница 24)
и.• ситуации, свидетельствующие об ошибке в самой программе и требующие её исправления (например, переменная принимает значение, которое по тем или иным причинам принимать не должна).Опишем теперь класс Error, соответствующий понятию «любая ошибка». В полном соответствии с принципами объектно-ориентированногопрограммирования перейдём от общего к частному, унаследовав от класса Error подклассы UserError, ExternalE rror и Bug для обозначения,соответственно, пользовательских ошибок, внешних ошибок и ошибок впрограмме.
От класса UserError, в свою очередь, унаследуем классыIncorrectInput, WrongFileName, IncorrectPassword и т. д.После этого обработчик видаcatch(const IncorrectPassword& ex) { / * . . . * / }будет обрабатывать только исключения, связанные с неправильным паролем, тогда как обработчик видаcatch(const UserError& ex) { / * . . . * / }будет реагировать на любые ошибки пользователя, что же касается обработчикаcatch(const Error& ex) { / * .
. . * / }то он сможет «поймать» вообще любое исключительное событие из нашейиерархии.Подчеркнём, что такое п реобразование работает только для адресов, а не для объектов как таковых; именно поэтому мы в нашем примере в заголовках catch использовали ссылки.1115. ШаблоныВ программировании часто возникают ситуации, в которых простейшим и очевидным решением оказывается написание нескольких почтиодинаковых (а иногда и совсем одинаковых) фрагментов кода. Известно,что в таких случаях пойти «очевидным» путём — идея крайне неудачная,ведь тексты программ часто приходится изменять, исправлять ошибки,добавлять новые возможности, и если некий фрагмент кода будет существовать более чем в одном экземпляре, вносить изменения придётсясинхронно в каждый из экземпляров.
Практика показывает, что раноили поздно мы про какой-нибудь из экземпляров забудем, исправив всекроме него; впрочем, даже если бы не это, механически дублировать одни и те же правки в нескольких местах программы оказывается деломутомительным и неприятным.Если фрагменты, которые хочется написать для быстрого решениявозникшей задачи, различаются только некоторыми значениями (илине различаются вовсе), бороться с дублированием кода легко: достаточно вынести «сомнительный» фрагмент в отдельную функцию, и вместонескольких почти одинаковых кусков кода в нашей программе появятсявызовы этой функции; различаться они будут значениями параметров,причём заметить отличия при чтении такого кода будет, разумеется, гораздо проще, чем если бы приходилось каждый раз сверять большиефрагменты. Аналогичным образом обстоят дела при различии по именам переменных: мы можем в таком случае написать функцию, в которую будет передаваться адрес соответствующей переменной.Но что делать, если нам потребовались почти одинаковые фрагменты,различия между которыми не сводятся к значениям выражений? Самыйпростой и часто встречающийся пример такой ситуации — различие т и пов переменных и выражений; как поступить, если нам потребовалосьвыполнить одни и те же действия, но в одном случае — над переменными типа in t, а в другом — над переменными типа double?В языке Си в таких ситуациях приходится прибегать к определениюмакросов, причём часто — многострочных.
Каждый программист, написавший хотя бы один многострочный макрос на Си, знает, какое это112утомительное и неблагодарное занятие. В случае, если в определениемакроса вкралась ошибка, найти её будет непросто, причём даже не сразу становится понятно, что ошибка кроется где-то в макросе, посколькукомпилятор в сообщении об ошибке указывает не на тело макроса, а нато место, где макрос вызван. Вообще, макропроцессор языка Си, из соображений совместимости включённый и в С и + + , представляет собойсредство опасное в применении и крайне неудобное, что многократноотмечают многие авторы, включая Страуструпа. Однако вред от дублирования кода оказывается ещё больше, так что при работе на Си в рядеслучаев иного выхода не остаётся.В языке Си-|—Ь из большинства таких положений можно выйти, используя шаблоны, которые делятся на шаблоны функций и шаблоныклассов.
Общая идея тех и других состоит в том, что мы пишем как бызаготовку для функции или класса, которая при конкретизации некоторых параметров может превратиться в настоящую функцию или, соответственно, класс. В качестве параметров шаблона чаще всего выступаютимена типов выражений, но могут выступать и значения целого типа, ав некоторых специфических случаях и адресные выражения. Разумеется, один шаблон можно использовать для создания нескольких разныхфункций или классов, отличающихся друг от друга только значенияминекоторых параметров. В а ж н о поним ать, что ш аблон — это ещ ёне код, это л и ш ь заго то вк а д л я кода.
Вполне допустимо восприятиешаблонов как этаких «умных макросов».§5.1. Ш аб л о н ы ф у н к ц и йРассмотрим для примера функцию, сортирующую массив целых чисел методом «пузырька» (конечно, этот метод неэффективен и в реальной жизни его лучше не применять, но для примера он вполне подойдёт):vo id s o r t _ i n t ( i n t * a r r a y , in t len ) {bo ol done;do {done = tr u e ;f o r ( i n t i= 0 ; i < l e n - l ; i+ + )if( a r r a y [i] > a r r a y [i+1]) {int trap = a r r a y [i];a rra y [i] = a r r a y [i+1];a r r a y [i+1] = tmp;done = false;}} w h ile ( ! d o n e);}113Пусть теперь нам потребовалась такая же функция для сортировкимассива из чисел типа d o u b le .
Использовать имеющуюся функцию м ы ,понятное дело, не сможем: числа с плавающей точкой совершенно иначесравниваются и имеют другой размер. Между тем, если мы всё-таки напишем функцию s o r t _ d o u b le , она будет отличаться от s o r t _ i n t всегов двух местах: в типе параметра a r r a y и в типе временной переменнойtmp, больше нигде. Поскольку дублировать код таким образом совершенно недопустимо, приходится как-то выкручиваться. В языке Си для этого пришлось бы создать с помощью директивы # d e f in e многострочныймакрос с одним параметром, задающим как раз тип элементов сортируемого массива и, соответственно, тип переменной tmp.В С и + + проблемы, подобные вышеописанной, можно решить с помощью шаблонов.
Как уже говорилось, шаблон — это своего рода «заготовка» для функции; сам по себе шаблон функцией не является, номожет быть в неё превращён, если указать значения параметров. В данном случе параметром шаблона будет тип элементов массива. Опишемэтот шаблон:tem p late « c l a s s Т>vo id s o r t (Т * a r r a y , in t len ) {bool done;do {done = t r u e ;f o r ( i n t i= 0 ; i < l e n - l ; i+ + )i f ( a r r a y [i+ 1 ] < a rra y [ i] ) {T tmp = a r r a y [ i ] ;a r r a y [ i] = a r r a y [ i + 1 ] ;a r r a y [i+ 1 ] = tmp;done = f a l s e ;}} w h ile ( ! d o n e);}Поясним, что ключевое слово t e m p la t e указывает компилятору, что далее последует шаблон.
Затем в угловых скобках перечисляются парам е т р ы ш аблона, причём слово c l a s s означает на самом деле произвольный т и п , а не только класс. Таким образом, наш шаблон имеет одинпараметр (с именем Т), в качестве которого ожидается указание некоеготипа. Далее следует тело функции, в котором идентификатор Т используется в качестве типа.
Ясно, что такое тело невозможно откомпилироватьв объектный код, поскольку неизвестно, какой тип обозначается именемТ, как выполнять индексирование в массиве из таких элементов (ведьразмер элемента тоже неизвестен), как выполнять присваивание и сравнение.
Мы получили, таким образом, не функцию, а лишь «заготовку»функции. Эта заготовка (шаблон) имеет имя s o r t .114Чтобы теперь заставить компилятор построить функцию на основешаблона, достаточно указать конкретный тип, который будет использоваться вместо Т. Это делается тоже с помощью угловых скобок; выражение so rt< in t> обозначает функцию для сортировки целочисленныхмассивов, полученную из шаблона so rt с использованием типа in t в качестве значения параметра Т:in t а [30] ;И ...s o r t< in t> (a , 30);Таким же точно образом sort<double> обозначает функцию для сортировки массивов из чисел типа double. Более того, шаблон годится длясортировки массивов из элементов произвольного типа, нужно только,чтобы для этого типа существовал конструктор по умолчанию (впрочем,иначе не смог бы существовать и массив), были определены операция <,используемая в нашем шаблоне для сравнения, и операция присваивания.Получение функции из шаблона называется и н стан ц и ац и ей шаблона.
Компилятор инстанциирует каждую функцию только один раз, т. е.если в нашем модуле трижды встретится вызов функции so rt< in t> , коддля неё будет сгенерирован лишь единожды.Интересно, что в ряде случаев инстанциированную функцию можновызвать, не указывая параметры шаблона; в частности, вызовs o r t (а , 30);будет вполне корректным. По типу параметра а компилятор «догадается», что имеется в виду именно so rt< in t> . Эта возможность называетсяа в т о м а т и ч е с к и м выводом а р г у м е н т о в ш аб л о н а; забегая вперёд,отметим, что для шаблонов классов такой возможности нет.§5.2.