А.В. Столяров - Введение в язык Си++ (1114949), страница 25
Текст из файла (страница 25)
Ш аб л о н ы классовЧтобы понять, зачем нужны шаблоны классов, вспомним пример, который мы приводили в § 2.20. Напомним, что мы рассматривали р а зр е ж е н н ы й м ас си в целы х чисел. При этом мы написали классSparseArrayInt и подчинённый ему класс Interm.Если нам потребуется теперь разреженный массив чисел другого типа или, скажем, разреженный массив символьных строк, то код классаSpar seArray Int придётся полностью дублировать с минимальными изменениями, что, как мы уже говорили, недопустимо. Существенно правильнее будет превратить существующий код для целочисленного массива в шаблон массива произвольного типа.
Перепишем заголовок класса,приведённый на стр. 64, в виде шаблона:115tem p late < c la s s T>c l a s s SparseA rray {s t r u c t Item {in t in d e x ;T v a lu e ;Item * n e x t ;};Item * f i r s t ;p u b l ic :S p a rseA rray () : f i r s t ( O ) { }"S p a rse A rra y ( ) ;c l a s s Interm {f r ie n d c l a s s SparseA rray<T >;SparseA rray<T> *m a ste r ;in t in d e x ;Interm (SparseA rray<T> *a _ m a ste r, in t ind): m a ste r(a _ m a ste r), in d ex (in d ) { }T& P r o v id e (in t i d x ) ;vo id Remove(int id x ) ;p u b l ic :o p e ra to r T ( ) ;T o p e r a to r = (in t x ) ;T o p e ra to r+ = (in t x ) ;T o p e rato r+ + ();T o p e r a t o r + + ( in t ) ;};fr ie n d c l a s s Interm ;Interm o p e r a t o r [ ] ( in t id x ){ re tu rn In te r m (th is, i d x ) ; }p r iv a te :S p a rseA rray (c o n st SparseArray<T>&) { }vo id o p e ra to r= (c o n st SparseArray<T>&) { }};Обратите внимание, что везде к слову SparseArray мы добавляем параметр <Т>, за исключением имени класса и имён конструкторов и деструкторов.
Дело тут в том, что относительно самого класса (т. е. шаблона)компилятор и так знает, что описывается шаблон, и то же самое можносказать про конструкторы и деструкторы; когда же речь идёт о типахпараметров в функциях, о типах указателей, и, наконец, о конкретномдружественном классе — то теоретически в качестве таковых могут выступать любые классы, в том числе созданные из этого же шаблона, нос параметром, отличным от Т. Поэтому тип приходится указывать полностью.116Функции-методы нашего шаблона класса тоже придётся описыватькак шаблоны.
Например, описание деструктора примет следующий вид:tem plate < c la s s Т>SparseA rray<T >: : "S p a rse A rra y () {w h ile (fir s t ) {Item *tmp = f i r s t ;f i r s t = fir st- > n e x t;d e le te tmp;}}а операцию присваивания из класса Interm нужно будет описать так:tem plate < c la s s Т>Т SparseA rray<T >: : In te rm ::o p e ra to r= (T х){i f (х == 0)Rem ove(index);e lseP ro v id e (in d ex ) = x;re tu rn x ;}Обратите внимание, что при раскрытии области видимости нам приходится в явном виде указывать, какой класс (а не шаблон класса) мы имеем в виду, то есть писать SparseArray<T>, а не просто SparseArray.
Это ипонятно: соответствующий метод будет присутствовать в каждом классе,построенном по нашему шаблону, но ведь это будут разн ы е методы (хотяи построенные по одному и тому же ш аблону метода). В ообщ е, везде, гдепо смыслу п р ед п о л агается и м я т и п а , м ы д ол ж н ы при использованииш аблона у к а з а т ь зн ач ен и я для п а р а м е т р о в ш аблона, ч т о б ы п о л у ч и т ьт и п ; имя шаблона класса без угловых скобок и параметров используется только в трёх случаях: в начале описания шаблона (когда, собственноговоря, з а д а ё т с я имя шаблона), при описании конструктора и при описании деструктора.Описать шаблоны для остальных методов SparseArray предоставимчитателю самостоятельно в качестве упражнения.§5.3.
С п ец и ал и зац и я ш аблоновЯзык С и + + позволяет задать свой (отличный от общего) вид шаблона для частных случаев его параметров — попросту говоря, правиловида «в таком случае генерировать вот так, во всех остальных случаях —в соответствии с общим шаблоном».117Допустим, нам захотелось использовать шаблон функции сортировки,приведённый на стр. 114, для сортировки массива указателей на строки(указателей типа char*), чтобы расположить соответствующие строкив лексикографическом («алфавитном») порядке.
Проблема в том, чтошаблон в той его версии, которая нами рассматривалась, использует длясравнения элементов операцию «<», а эта операция хотя и определенадля указателей, но никакого отношения к алфавитному порядку строкне имеет. Решение, однако, оказывается достаточно простым. Для началазаменим знак «меньше» на вызов функции, которую назовём, например,so r t_ le s s:tem p late « c l a s s Т>vo id s o r t (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+ + )if(sort_less(array[i+1],array[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);}Саму функцию s o r t_ le s s опишем тоже с помощью шаблона:tem plate « c la s s Т>bool s o r t_ le s s(T а , Т b) { return а < b; }В результате для всех типов, для которых имеется операция «меньше»,шаблон so rt будет продолжать работать так же, как работал до переделки; функцию s o r t_ le s s компилятор будет каждый раз генерироватьавтоматически как обычное сравнение.
Нам осталось только предусмотреть особый случай для сравнения элементов типа char*, и тут мы какраз и прибегнем к явной специализации.Описание случая явной специализации начинается, как обычно, сослова tem plate, но угловые скобки после него оставляются пустыми, чтобы показать, что это, с одной стороны, шаблон, но, с другой стороны,э т о т шаблон пишется для частного случая, не зависящего от дополнительных параметров. Далее записывается имя шаблонной функции,причём в некоторых случаях1 необходимо вместе с именем указать всеИ нашем случае это не обязательно, поскольку параметр шаблона выводится изтипа параметров функции, так что параметр шаблона в имени функции можно опустить; но в случае, когда параметр шаблона с типами параметров функции не совпадает, указать его всё же придётся.118параметры шаблона (в нашем случае — один параметр).
Всё вместе выглядит так:te m p la te Obool so rt_ le ss< c o n st char*>(const char * a , const char *b){return strcm p(a, b) < 0;}Для компилятора это означает примерно следующее: «будь любезен,шаблон s o r t_ le s s для случая so rt_ le ss< c o n st char*> обрабатывай всоответствии с вот этим текстом шаблона, а не каким-либо иным».Естественно, и шаблон класса тоже можно подвергнуть специализации.
Например, если нам потребуется разреженный массив элементов типа bool, мы можем, конечно, воспользоваться шаблоном из § 5.2, но, каклегко заметить, такая реализация окажется несколько странной: ведь типbool имеет всего два значения, а для разреженного массива это значит,что, если элемент вообще хранится в объекте, то этот элемент имеет значение tru e (поскольку если бы он имел значение f a ls e , он бы в объектене хранился: массив-то разреженный).
Следовательно, элементы спискаItem для этого случая логично бы сделать состоящими из двух, а не изтрёх полей (то есть хранить номер элемента и указатель на следующийэлемент, а значение не хранить, поскольку оно и так известно). Большетого, множество целых чисел нам может показаться удобнее хранить вмассиве, а не в списке, и т. д. Механизм специализаций позволяет создатьотдельное описание шаблона SparseArray для случая Т == bool:tem plate Оc la s s SparseArray<bool> {/ / . . . реализация булевского разреженного массива . . .Для шаблонов классов язык С и + + предусматривает ч асти ч н у юспециализацию , при которой специализирующий вариант шаблона сампо себе тоже зависит от параметров. Такой специализатор начинаетсясо слова tem plate, после которого следует непустой список параметровшаблона; при этом параметры в заголовке шаблона, вообще говоря, могут не совпадать (или совпадать не полностью) с параметрами описываемого шаблона; при этом фактические параметры шаблона указываютсяв угловых скобках после его имени.