Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 87
Текст из файла (страница 87)
Наличие одного и того же параметра шаблона для базового и производного классов является самым распространенным случаем, но это нс обязательное требование. В интересных, хотя и реже встречаемых случаях, используется передача самого производного типа в базовый класс. Например: ~енты !У базовые о пера чаи с кон шеи нералт гетр!аге <с!акя С> с!аяк Ваис орк ( рибйс Ьоо! орегагог== (сопИ С1) сопк1; О сравнивает есе зле> Ьоо! орега1ог!= (сопк1 Сй) сопяб I! // обеспенпеает доступ к операяияя класса С сопИ СЪ с!ег!иес!() сопв1(ге!ига в1апс сак1<сопвг СК >("1Ыв); ) 1етр!а1е<с1акк Т> с!аяв МагЬ соп1атег: риЫ1сВая!с оря<МагЬ соп1атег Т' > ( риЫ!с я1ее 1я!ее()сопя1, Т8 орега1ог() (вгее 1) сопИ ТЬ орега1ог() (к!ее 1) сон к1; 0- ), Это позволяет определить базовые операции с контейнерами один единственный раз и отделить их от опрелеления самих контейнеров.
Однако определение операций, таких как == и ) =, должно быть выражено в терминах и контейнера п его элементов, поэтому базовый класс должен быль передан в шаблон контейнера. Предположив, чтоМа1Ь сои!а!пегподобен традиционному вектору, определения членов Ваяс оря мо~ли бы выглядеть следуюц!им образом: 1етр!аге <с!авв С> Ьоо! Вав1с орк<С>сорега1ог== (сопя! С!( а) сопя! ( Дбег!ие<!().к!ее ы а яссе ()) ге!игл 5а!ве; Можно рассматривать подобные примеры в качестве иллюстрации того, как шаблон используется для обеспечения элегантного н безопасного с точки зрения типов интерфейса к средству, которое в противном случас было бы неудобно использовать.
Естественно, часто бывает полезно создавать шаблоны, производные от другого шаблона. Базовый класс в этом случае является строительным блоком для реализации дальнейших классов. Если члены базового класса зависят от параметров шаблона производного класса, сам базовый класс должен быть параметризировац. Хорошим примером может служить (тес из ч 3.7.2; 398 Глава 13. Шаблоны 1ог 1!л11= О, ! < йепией 3 в!ее у; -нч) (Т! йепией 3 1!) и аЯ ге1агп Та!ве; ге1игл 1гие, Альтернативной техникой разделения контейнеров и операций янляется их комби- нирование через аргументы шаблона (а не посредством наследования): гетр!аге<с1аве Т, с1авв С с1аев Мсоп!а!лег( С е1етел ге; раЫ!с, Т& орега1огЦ (в!ее 18(ге1игп е!етеп1е(1) ] !! сравнелие элел~елтоа Тпепй Ьоо!орега1ог==<> !сопе1 Мсолга!пег&, соле! Мсол1а1пег&!; ~пепй Ьоо1 орегагог~< > (соле! Мсол!а!лег&, соле! Мсолгатег&) 1етр!а1е<с!аее Т> с!аль Му апау(/* ...
л! ); Мсол1а1пег йоиЫе, Му апау<йоиЫе» тс; Класс, генерируемый из шаблона класса, является обычным классом. Следовательно, у него могут быть функции-друзья Я В.13.2). В нашем случае я воспользовался функциями-друзьями для достижения привычного симметричного стиля аргументов операторов -= и 1- Я 11З.2). Можно также рассмотреть вариант с передачей шаблона вместо контейнера в качестве аргумента С Я В.13.3). 13.6.1. Параметризация и наследование Шаблон является механизмом парамегризации определения класса или функции произвольным типом. Код, реализующий шаблон, идентичен для нсех типов параметров, также как и большая часть кода, использующая шаблон.
Абстрактный класс определяет интерфейс. Болыпая часть кода различных реализаций абстрактного класса может совместно использоваться в иерархии к.лаосов, и большинство фрагментов, использующих абстрактный класс, не зависит от его реализации. С точки зрения проектирования оба подхода настолько близки, что заслуживают общего пазнания. Так как оба метода позволяют выразить алгоритм один раз и использовать его со множеством типов, их вместе часто называют лолиморфлил~и. Для того чтобы все-таки нх различать, то, что обеспечивает виртуальные функции, называют полил~орфизмом времени вьтолнения, а то, что предоставляют шаблоны — полиморфиэмом врвмепи комли!!яции или параметрическим полилюрфизмом. Итак, когда мы выбираем шаблон, а когда полагаемся на абстрактные классы? В любом случае мы работаем с объектами, которые совместно используют общий набор операций.
Если между этими объектамн не требуется иерархической зависимости, лучше использовать их в качестве аргументов шаблонов. Если фактические типы этих объектов не известны во время компиляции, их наилучшим представлением являются классы, производные от общего абстрактного класса. Если целью является эффективность на этапе выполнения, то есть большое значение имеет встраивание операций, следует использовать шаблоны. Эти вопросы подробнее обсуждаются в э 24гй1, 399 13.6. Наследование и шаблоны 13.6.2.
Члены-шаблоны Класс или гпаблоц класса может иметь члены, которые сами являются шаблонами Например: 1етр!а1е<с1акк $са!аг> с1азз сотр1ех ( 5са1аг ге, ип, рибйс: гетр!а1е<с!изз Т> сотр!ех (сопз1 сотр!ех<Т й с) ге (с геа! ()), гт (с!таК $ () сотр!ех<Яоас> с/(О, О), нотр!ех<г!оиЫе> сг( = с), // привольно: используегпся преооризовиниетгои1 в г(оиб!г> с1акк 11иаг(( // нет преобразования в го1 ); сотр!ех«'1иаа> с>1; нотр!ех<гп1> сг = со; //ошибка: нет преобразования Яг>ий в го Другими словамн, вы можете создать сотр1ех<Т!> из сотр(ех<Т2> в том и только в том случае, если вы инициализируете Т! при помогди Т2.
Это выглядит разумно. К сожалению, Ся-ь допускает некоторыс неразумные преобразования встроенных типов, такие как с(оиЫе в 1п1. Проблемы потери значения можно обнаружить во время выполнения прп помощи проверяемого преобразования в стнле !тр1!с!1 саз1 Я 13.3.1) и функции с!>есяег1 () Я В.6.2.6): 1етр!а1е<с1азз 5са1аг> с!акз согпр!ех ( $са!аг ге, гт, риЫгс. сотр!ех (): ге (О), ип (О) () сотр1ех (сопк1 нотр!ех<5са!аг>! с): ге (схеа! ()), гт (слтая()) () 1етр!а1е<с!акк Т2> сотр1ех (сопк1 нотр!ех<Т2>с. с), ге (с)гесвег( саз1<$са!аг> (с геа! ())), гт (сЬесвег( сазг<$са!аг> (с.!тад 0)) () О..
), Для полноты картины я добавил конструктор по умолчанию и коппрукнцнй конструктор. Любопытно, что конструктор шаблона нико~да не используется для гсперации копирующего конструктора 1так, чтобы при отсутствии явно объявленного копирующего конструктора, генерировался бы копирующий конструктор по умолчанию). В этом случае сгенерированный копирующий конструктор был бы идентичен тому, что я явно указал.
Аналогично, копирующее присвапванпе Я 10А.4.1, <) 11.7) должно быть определено как нешаблонный оператор. Член-шаблон не может быть виртуальным. Например: с1акк 5!>аре ( // // ошибка> вггрп>кильньг>1 шаблон Глава 13.шаблоны 400 Гетр1а1е<е1акк Т о1гГиа1 Ьоо1 т1егзесг)сопк1ТВ) сопзг=д; Это недопустимо. Если бы такое было дозволено, нельзя было бы пользоваться традиционной техникой с таблицей виртуальных функций Я 2.5.5) для реализации виртуальных функций. Компоновщику пришлось бы лобавлять новую точку входа в виртуальной таблице для класса 5йаре каждый раз, когда кто-нибудь вызывает га1егзес1 )) с новым типом аргумента, 13.6.3.
Отношения наследования Шаблон класса обычно воспринимается как спецификация того, как должны создаваться конкретные типы. Другими словами, реализация шаблона является механизмом генерации необходимых типов на основе спецификации пользователя. Поэтому шаблон класса иногда называют генератором типов. С точки зрения правил С++ два класса, сгенерированные из одного шаблона, не связаны друг г другом никакими отношениями. Например; с1акк ЯЬаре 1/' .. "/); с1акз Сггс1е: риЬйе $Ьаре ( /" ... */); При наличии этих объявлений, иногда пытаются обращаться с зе1<Снс1е"> как с ке1<5йаре" >.
Это серьезная логическая ошибка, основанная на неверном рассуждении: «Си с1е является 5йаре, поэтому множество объектов С1гс1е также является множеством объектов 5/заре, следовательно я могу использовать множество объектов С1гс1е как множество объектов 5йаре». В этом рассуждении «следовательно» не верно. Причина: множество обьекгов типа Сис1е гарантирует, что членами множества являются объекты тина С1гс1е, множество объектов типа 5йарене дает такой гарантии.
Например: с1акк Тггапн)е риЬ11с ББаре(/* ... "/), иог«Ц)кег<5Ьаре" е з) ( //- кдпкегГ )пеш Тг)апд1е ))); 0- эоЫи)ке1<С)ге1е'>1' к) ( з )з); //о~пипки несоответствия типот к — это зе1<Сйс)е*>, и не зег<бэпре« ) Подобный код не может быть скомпилирован, потому что нет встроенного преобразования зе1<С1гс1е*>8 в зе1<5/эаре*>к. И не должно быть.