Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 62
Текст из файла (страница 62)
В любом случае, почти все ошибки такого рода быстро обнаруживаются во время выполнения -- дата 26 июля 27 года далеко нс часто встречается в моей повседневной работе. Что делать с датами до 1800 года (плп гдето около того) — вопрос довольно топкий, и мы оставим его экспсртам-историкам. Более того, число нельзя до конца проверить отдельно от значении месяца н года. В ь 11.7.1 приводится способ определения удобного в использовании типа Уеаг Где-то должна быть определена корректная дата по умолчаншо.
Например: Риге ОаЕе:е(е)аи(Е с(а Ее )22, Еал, 1901); Я убрал кэш, использовавшийся в 9 10.2.7.1, потому что для такого примитивного типа он просто не нужен. При необходимости его можно добавить как деталь реализации — па пользовательский интерфейс это не повлияет. Приведем небольаеой пример использования Раге: ооЕЕЕ Е )Рагеб ЕЕ) ( ОпЕе Еоб дау = Росе )Еб, Опте: йес, с(уеиг ))); ЕГ )с(.е(ау ))==29 ЬЕ д топгй )'==Рагс.ХеЬ) 1 Ь'..
11 )тгдп19ЬЕ))) йасЫ дау)1); соиЕ « "следующий день. ' «ЕЕь1 « '~п'; В примере предполагается, по операторы вывода «и слолссния+ объявлены в Ра(е. Я лелаю это в 1) 10.3.3. Обратите внимание на форму записи РпЕе::~еЬ. Функция Д не является членом Оаге, поэтому в ней требуется явно указывать, что имеется в виду 1еЬ из Ра(е, а не какой-либо другой объект. Зачем нужно определять специальный тип для такого простого понятия, как дата? В конце концов, мы могли бы определить серуктуру: есгисЕ РаЕе ( !гд Йау, толЕЬ, уеаг, и позволить программистам решать, что с ней делать. Однако если бы мы поступплп таким образом, каждый пользователь должен был бы манипулировать компонентамп.Ра(е непосредственно, либо реализовать для этого отдельные функции. В результате, понятие даты было бы «разьеазано.
по всей системе; его было бы сложно понимать, документировать и модифицировать. Реализация концепции в виде простой структуры неизбежно привела бы к дополнительным затратам со стороны каждого пользователя этой структуры. 285 10.3. Эффективные типы, определяемые пользователем Хотя тип еза1е н выглядит простым, придется затратить некоторые усилия на его реализапию. Например, при увеличешш даты надо помнить о високосных годах, надо не забывать, что в месяцах содержится разное количество дней н т.
д. 1Я ! 05(1(), Кроме того, представление в виде «день,смесяц/год» является недостаточным для многих приложений. Однако если бы мы захотели его изменить, нам потребовалось бы модифицировать только набор соответствующих функций. Например, для того чтобы представить Ва1е в виде количества дней до или после 1 января 1970 года, нам пришлось бы изменить только функции-члены еса1е (8 10.6[2(). 10.3.1.
Функции-члены Естественно, где-то должна находиться реализация каждой функции-члена. Приве- дем пример определения конструктора 1за1е: Васе: Васе (те йй, сс(опсИ тт, ЫС уу( ( ст (уу == О( уу = йе)аи(Г йоге.уеиг ((, су (тт == 0( тсп = йехие йаге топей ((; (т" (йй == О) сЫ = йехие йа!е.йау ((, спс тих; звнсЬ (тт(( сизе~еЬ: тах = 28Чеаруеаг (уу(, Ьгеау; сазе арг саке сисс, саке зер; газе поо. тах.= 30, Ьгеай; сазе)ап сазе таг саве тау.
сазе)ой саве аид; саке оей сазе йег тих=Об Ьгеау, йе)а ил ГЬгосо Вой йасе ((, ссгс кто-то что-то напутал (с' (сЫк! (( тах<йй( гйгот Вай йасе ((; у = уу; т =тт; й = сЫ; Конструктор проверяет, имеет лн дата допустимое значение. Если нет, например в случае Ва(е (30, Ра1е уеб, 199о(, он возбудит нгключенис (~ 8 3, глава 14), что означает: случилось нечто, что нельзя проигнорировать.
Если данные имеют доссустпмые значения, производится очевидная инициализация. Инициализация является относительно сложной операцией, потому что включает в себя проверку корректности данных. Это вссьма типично. С другой стороны, после того как объект типа.0а1е создан, им можно пользоваться н копировать без дополнительных проверок. Другнмп слонами, конструктор устанавливает инвариант класса (в данном случае, это означает допустимость даты). Другие функцпи-члены могут полагаться на этот инвариант Глава 1О.
Классы 288 и должны сохранять его. Такая техника проектирования может значительно упростить код (см. 5 2гь3.7.1). Я использую значение Моиг!с (О( — что не соответствует ни одному месяцу — в качестве указания евоспользоваться датой по умолчанию». Я мог бы определить элемент перечисления Моп!6, чтобы выразить это.
Но я решил, что лучше воспользоваться очевидно недопустимым значением для указания на дату по умолчанию, вместо того, чтобы создавать видимость существования 13 месяцев в годч. Обратите внимание, что нулем в этой ситуации можно пользоваться, потому что оп гарантированно находится внутри диапазона значений перечисления Моп1!с (8 4.8). Я подумывал о вгяделении проверки данных в отдельную функцшо св с!а!в ((. Однако я счел результирующий код более сложным и менее надежным, чем код, использующий исключение.
Например, предположим, что в 2!а1е определена операция ввода»: ио!с!1с!! (вес! ог<Ва 1е>й о а( сиЬ!!е (с!л(( Ваге с1; !гу ( с!л» с(; са1с6 (Ва1е;Вас! с!а1е(( //мов обработка ошабки сол илие; аа рив6 6ас6 (с!(; О ся, э" З.Г.3 !и!те !псйасе. с!ау (( соле! ге1игп с(; Васей!>а1е: ас!с! топ!6 (!псп( ( (1 (а==у( ге!игл*16!в; (! (п>0(( !п1с!ейа у=п/!2; ан тт = т»п%!2; Ч(!г Н * с!е!1а у-н-; тт -= !2; ) (! обратите вннлоние; гл!(дес) == l2 сс' обработки ситуации, когда в Л !оп!6 (тт) не суи!ествуеп~ днн с! у+= с!ейа у, т = Мол!6 (тт(; Как это обычно и происходит в случаях с такими простымп конкретнымп тппамц, ело'кногть определения функций-членов колеблется от «тривиально» до «не слиш- ком сложною Например; гз? 10.3. Эффективные типы, определяемые пользователем ге!ига '!Ь!в, // обработка отрицоспельного значения п ге!игп '!Ь!с 10.3.2.
Функции-помощники О числодней в диапазоне(о, Ь) я.ш (Ь. о) // год оисокосньш)? сп! йй(Ва!е а, Ва!е Ь); Ьоо1!еаруеаг (1п! у); Васе пех! сиееуйау (Ра!е й), Ва!е пех! са!игйау (Расе й); Определение таких функций в самом классе усложнило бы интерфейс класса и потенциально увеличило бы количество функций, которые прищлось бы просматривать прн изменении представления. Как эти функции лсвязаньн с классом Ва!е? Традиционно, объявления такого рода функций просто помещались в тот же файл, что п обьявление класса Ва1е, и пользователи, которым потребовался Ва1е, делали их доступными путем включения этого файла, определяющего интерфейс (~ 9.2.1). Например: № с ос)ис(е 'Ваге. Ь" Вместо использования Ва1е./ч — или в качестве альтернативы — мы можем устано- вить связь явно, поместив класс, вместе с его функциямп-цомощникамп, в простран- ство имен Я 82): // средство роботы го вреяенел патеарасе С!топо ( с1асс Ра!е ( /* .— / ) ш! йф(Ра!е а, Ва!е Ь); Ьоо! 1еаруеаг )пс! у), Ра!е пех! тееуйау )Ва !е й), Расе пех! виЕигйау(Ра!ейн //- Пространство имен С)сгони, естественно, будет содержать в себе связанные классы, такие как 1)те и о!орта(сй н нх функции-помоьцники.
Использование пространства имен только для одного класса обычно является избыточным и вызывае~ определен- ные неудобства. 10.3.3. Перегрузка операторов Часто бывает полезно пеиеть функпнц, обеспсчявщосане привычную форму записи. Например, функция орега1ог=- определяет оператор проверки на равенство == для работы с Ва1е: Как правило, у класса есть набор функций, связанных с ннм, но не требующих опре- деления в классе, потому что они не нуждаются в непосредственном доступе к пред- ставлению.
Например; Глава 10. Классы 288 гл!!ле Ьоо! орегагог — — — (0а1е а, Ва!е Ь) /1 проверка на равенсп~во ге!игл а г!ау ()==Ь г!ау () &&а топ!Ь ()==Ь толгЬ () йй а уеиг ()==Ь уеаг ~), Другими очевидными кандидатами являются: >!7 не равно г'! менмие 1> бовыие Ьоо! арега1ог~= (Ва !е, Ра 1е); Ьоо! ореги1ог (Ра1е, Ра1е); Ьоо! орега1ог> (Ра1е, Ро1е), 1/ инкремент )/ декрелгент !г' прибавить и дней 1! вичеспг» и днеи Ва1е орега1ог++(Ра!ейд), Ва!е орега1ог — (Ра!ей!!), 0и1е орега1ог+= (Ва1ей И, !л1л); Ва1е орега1ог=-(Ва1ей е1, слг л); 0 прибаатль л днев г г вычесть и дней Ра1е ирека!о г» (Ва!е а', ьл1 л); Ра1е орега1ог- (Ваге д, !л1 л), 71 вивести д ,1,! считать в д овггеат& орегагог«(ов!геатй,оа1еф, !вггеатй орега1ог> (!в!геа>п&,Ра1ейд), 10.3.4.
Роль конкретных классов Я называю простые типы, определяемые пользователем, такие как Ва1е, конкреп!нь!- лп! талал!и, чтобы отличать пх от абстрактных классов (8 2.5А) и иерархии классов (!у 12.3), а также, чтобы подчеркнуть их сходство со встроенными типами, такими как !л1п ейаг. Конкретные типы называют еше типами значении, а их использование -. програмзгарпванием, орнентированньы! назначения. Модель использования и чфплософия» проектнровшпля конкретных типов сильно отличается от того, что часто называют объектно-ориентированным программированием (() 2.6.2), Конкретные типы служат для быстрого и эффективного решения одной небольшой задачи.
Как правило, пользоватсл!о пе предоставляется средств для измспсппя поведения конкретного типа. В частности, конкретные типы не демонстрируют попиморфное поведение (см. ~ 2.5.5, 6 12.2.6), Если вам не нравятся некоторые детали конкретного типа, вы создаете новый с желаемым поведением. Гслп вы хотите т!овторно использовать> конкретный тпп, вы используете его при реачизацип вашего нового типа точно так же, как вы могли бы применить !л1. Например: с1аввВа1е апь! Рте( рггоа1е Ва1е д, Тете 1; риб!!е: Для класса Ваге эти операторы можно рассматривать просто как удобство. Однако для мио! их типов — таких как комплексные числа (8 113), вектора (~ 3.7.1) и объекты-функции (з 16А) — использование стандартных операторов настолько привычно, что их определение становится просто обязательным. Перегрузка операторов обсуждается в главе 11.
10.4. Объекты 289 ь«аге ать Нте 1«»аГе Ы, Т!те Г); х»аГе апд Птае (гпГа,0аГе.МапГй т, юаГу, Тале Г1; О... Чтобы определить новые типы на основе конкретного типа путем описания соответствующих различий, можно воспользоваться механизмом производных классов, обсуждаемым в главе 12. Примером является определение )гес нз пес1ог19 3.7.2). При наличии поста гочно хорошего компилятора конкретные классы типа;0ауе не вызывают скрытых дополнительных затрат памяти или увеличения времени выполнения.