Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 88
Текст из файла (страница 88)
Ыяе ! = а. зйе () ) ге<и<и уаЬе! уог(т< 1=0; 1«<ег< е<1() .Яяе(); -~и) (Т(«ег(ге«() [<] ! = а [1] ) ге<игл Та<ее! ге<лги <гие; ) Другой техникой разделения контейнеров и операций является их комбинирование через параметры шаблонов (а не использование наследования): <етр<а<с<с<аз! Т, с1азз С> с1ат Мсоп<а<иег С с<степ<я; риЫ<с: Та орегатг [ ] (зае ! <) ( ге<игл е1етеа<я [<] < ) <г1еи4< Ьоо< орега<ог==<>сопя! Мсол<а<иега, соиз! Мсоп<а1иега ) ! <с<ел<1 Ьоо< прего<о! ( = < > (сопя! Меси<а(пега, соим Мсоп<а<лега ) < У ...
)< <етр<а<е<с<ат Т> с<аз!Му аггау ( /* ... *l ]; Мсол<а(лег«<оиЫе, Му а<!ау«<оиЫе» те < Класс, генерируемый из шаблона класса, является обычным классом. Следовательно, он может иметь дружественные функции (5С.]3.2). Я в данном примере воспользовался дружественными функциями, чтобы выразить симметрию операций == и (= (В)1.3.2). Можно было бы рассмотреть и вариант с передачей шаблона (шаблонный параметр классового шаблона) вместо контейнера для аргумента С (5С.]3.3).
Глава ) 3. Шаблоны 424 13.6.1. Параметризация и наследование Шаблоны являются механизмом параметризации определения типа или функции другим типом. Исходный код, реализующий шаблон, одинаков для всех типов параметров, как и большая часть кода, использующего шаблон. Когда требуется дополнительная гибкость, определяют специализации. Абстрактный класс определяет интерфейс.
Большая часть кода различных реализаций абстрактного класса может совместно использоваться в классовой иерархии, и большая часть кода, использующего абстрактный класс, не зависит от его реализации. С точки зрения проектирования оба подхода настолько близки, что заслуживают общего названия. Так как оба позволяют выразить алгоритм единожды и использовать его со многими типами, люди все зто называют полиморфизмом. Чтобы все-таки различать эти подходы, для виртуальных функций применяют термин полиморфизм времени выполнения (гип-йте ро(утозр(з!зт), а для шаблонов предлагают термин полиморфизм времени компиляции (сотр((е-!(те ро1утогр!з(зт) или параметрический полиморфизм (раготе!пс ро1утогр()(зт).
Итак, когда же следует выбирать шаблоны, а когда — абстрактные классы? В обоих случаях мы манипулируем объектами, которые разделяют (совместно используют) общий набор операций. Если отсутствуют иерархические взаимозависимости между объектами, лучше выбирать шаблоны. Когда же истинный тип объектов не известен на этапе компиляции, нужно опираться на классовые иерархии наследования от общего абстрактного класса.
Когда же особо важна производительность, обеспечиваемая за счет встраивания операций, следует использовать шаблоны. Эти вопросы подробнее обсуждаются в 524.4.!. 13.6.2. Шаблонные члены шаблонов Класс или классовый шаблон могут иметь члены, которые сами являются шаблонами. Например: !етр!а!в<с(азз оса!аг> с1азз сотр1ех /У детали см.
в З225 Юса(аг «е, 1т; риЫ(с: «етр1а«в<с(азз Т> сотр1ех (сопя«сотр(вх< Т> ь с): ге (с. гва1 ( ) ), (т (с. (тая () ) ( ) У... )' сотр1ех<(гоп!> с1'(д, 0); сотр!ех<аоиые> сд = с!) // о(<' используется приведение/(оа! к «(оиЫе с1азз Диод ( /У отсутствует приведение к !и! сотр1ех<(зиад> са« сотр1ех<т«> с(=сч; //епо«; нет приведения Сиад к !п! Другими словами, мы можете создавать Сотр!ех<Т1> из Сотр1ех<П> тогда и только тогда, когда вы можете инициализировать Т1 с помощью 72. Это выглядит разумно. ) 3.6.
Наследование и шаблоны К сожалению, С++ допускает некоторые неразумные операции преобразования встроенных типов, такие как з!оий!е в !иг. Сопутствующие проблемы потери точности можно обнаружить во время выполнения с помощью проверяемых преобразований в стиле илр!!с!1 сазг (~13.3.1) и функции едессе!!() ЯС.6.2,6): !етр1аге<е!азз Яса)аг> с1азз сотр1ех У детали см. в З22.5 ( »сагаг ге, йи; риЬЯс: сотр1ех (): ге (О), !т (д) ( ) совр)ах (соиз! сотр!ех<>са!аг>а с): ге (с.геа1 () ),1т (с. !тая() ) ( ) гетр!а!с<с)азз Т2> еотргех (соизг сотр1ех<Т2> а с) ге(сйесйей сазг<>са1аг> (с.
геа! () ) ), 1т (сйесдеа' саз«оса(аг> (с.!тая() ) ) ( ) Для полноты картины я добавил умолчательный конструктор и копирующий конструктор. Любопытно, что шаблонный конструктор (в этом примере — зависящий от параметра шаблона 22) никогда не используется для генерации обычного копирующего конструктора, так что в отсутствие явного определения последнего будет сгенерирован умолчательный вариант копирующего конструктора. В нашем случае такой неявно сгенерированный копирующий конструктор был бы идентичен тому, что я определил явно. Аналогично, операция присваивания 610.4.4.1, В11.7) также не должна определяться как шаблонная функция-член.
Шаблонная функция-член не может быть виртуальной. Например: с1азз овире ( У ... ггтргаге<сгазз Т> Пггиа1 Ьоо( тгегзес! (соиз! Ть ) соиле —.О; )' р еггог: гнг(иа1 (етр1а(е 13.6.3. Отношения наследования Шаблон класса полезно представлять как спецификацию того, как должны создаваться конкретные типы. Другими словами, реализация шаблона является механизмом генерации типов, основанная на предоставляемых пользователем сведениях. Как следствие, шаблоны классов часто называют генераторами типов Яре яепега1огз) . С точки зрения языка С++ два типа, сгенерированные из одного шаблона, никак не связаны между собой.
Например: Такое недопустимо. Если бы это было позволено, то для реализации виртуальных функций нельзя было бы применить традиционную технику виртуальных таблиц (В2.5.5). Компоновщику пришлось бы добавлять новую точку входа в виртуальную таблицу для класса Ьйаре каждый раз, когда из1егзесг() вызывается с новым типом аргумента. Глава ) 3. Шаблоны 426 с!авв овире ( /* ... */ ); с!авв С(гс!е с риЫ!с Б!саре ( /* ... */ ); Исходя из представленных объявлений люди часто пытаются трактовать вес< Ссгс1е* > как вес< Ясаре* >. Это серьезная логическая ошибка, основанная на неверном рассуждении; вС!гс1е зто Ясаре, так что множество объектов типа С1гс1е есть в то же время и множество объектов типа Я!яре; следовательно, я могу использовать множество объектов Ссгс1е как множество объектов Ясаре .
В этом рассуждении последняя часть не верна. Причина состоит в том, что множество объектов С1гс1е гарантирует, что каждый член множества есть С!гс1е, в то время как множество Ясара такой гарантии не дает. Например: с!ввв Тг!апе!е с риЫ(с Я1аре ( /* ... */ ); воЫ!'(вес<я)саре*>ь в) ( // ..
в.!пвегс(песо Тг(апе!е() ); // ... во!сС а (вес<С!гс!е* >а в) ( С(в); //егтот несоответствие типов: в есть вес<С>с(е*>, а не вес<о/саре* > ) Этот код не будет компилироваться, так как нет встроенного преобразования от вес< Ссгс1е* > а к вес<Я!яре* >а. И его не должно быть. Гарантия того, что члены множества вес<С1гс1е" > есть объекты типа Ссгс1е, позволяет нам безопасно и эффективно применять операции, специфичные для окружностей, например, определение радиуса, ко всем членам множества. Если бы мы позволили с вес<С)гс1е*> обращаться как с вес<Ясаре*>, то мы нарушили бы такую гарантию.
Например, функция Т() внедряет новый треугольник (объект типа Тгсапя1е) во множество вес<Ясаре*>, передаваемое ей в качестве аргумента. Тогда, если бы множество вес<Ясаре*> оказалось бы при вызовеУ() множеством вес<С!гс1е*>, была бы нарушена фундаментальная гарантия того, что все элементы множества вес<С!ге!е*> есть окружности. 13.6.3.1. Преобразования шаблонов В примере из предыдущего раздела демонстрируется, что не может существовать отношений по умолчанию между классами, сгенерированными из одного и того же шаблона. В то же время, для некоторых шаблонов нам хотелось бы такие отношения выразить.
Например, когда мы определяем шаблон указателей (объекты сгенерированных из шаблона классов ведут себя как указатели), нам хотелось бы отразить отношения наследования между адресуемыми объектами. Шаблонные члены шаблонов 513.6.2) позволяют выразить некоторые из таких взаимоотношений. Рассмотрим пример: ь уназатель на Т гетр!асс<с!авв Т> с!авв ри ( Т* рс Я27 ) 3.7.
Организация исходного кода риЫ!с г Ро (Т*); Ргг ( сопл! Ра а ); 77 копврутгяий консп1рукн!ор <етр!иге<с!азз Т2> орегатг Рп<Т2> (); Упреобразование Ргг -Т в Р(г Т2> л'... )' иой17(Р(г<С!гс!е> рс) ( Р(г<Ьпаре> рв = рс; Рп <Спсге> рс2 = рв; ) У должно работатн 77 даст ошибку Мы хотим допускать первую из указанных в данном коде инициализаций в том и только в том случае, когда о7(аре является прямым или косвенным открытым базовым классом для С!гс!е. В общем, нам нужно определить операцию преобразования так, чтобы преобразование от Рп<Т> к Ргг<П> допускалось тогда и только тогда, когда значение типа Т' можно присваивать объекту типа П*.