Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 42
Текст из файла (страница 42)
№Нпйе№ ТЕМРО АТЕ а НРР №С)Е№1ПЕ ТЕМРЕТЕ 6 НРР 222 Глава )2. Специализация и перегрузка гегигп х; // Объявление специализации запрещает инстанцирование // шаблона; определения здесь быть не должно // во избежание ошибки многократного определения Севр1асе<> йпг д(йпс, йпс у); 4епс)14 // ТЕМРЕТЕ 0 НРР Соответствующий файл реализации может быть таким: 41пс1ибе "гевр1аге д.)зрр" В качестве альтернативы специализация может быть встраиваемой; в этом случае ее объявление может (и должно) быть помещено в заголовочном файле. Полностью специализироваться могут не только шаблоны членов, но и обычные статические данные-члены и функции-члены шаблонов класса. Синтаксис требует наличия префикса гевр1аге<> для каждого шаблона класса Если специализируется шаблон члена, то для указания этого необходимо добавить префикс гевр1асе<>.
Чтобы проиллюстрировать это, представим, что у нас есть приведенные ниже объявления. // (1) // Объявление шаблона следует поместить // в заголовочном файле: Гевр1аге<сурепаве Т> 1ПС д(Т, Т х = 42) севр1асе<> йпс д(йпг, йпг у) ( гесигп у/2; ) 12.3.3. Полная специализация члена Севр1асе<сурепаве Т> с1азз Оисег ( ри)э11с:' Севр1аге<вурепаве У> с1азз 1ппег ( ргйчасе: згагйс 1пг соипгз ); зсагйс йпс собе; чоЫ ргйпг() сопят згс)::соис « "депегйс"з // (2) // (3] // (4) // (5) 12.3. Явная специализация 223 Сешр1аее<еурепаше Т> (пс Оисег<Т>::сос)е = б; // (6) сетр1аге<гурепагае Т> Гешр1асе<гурепаше О> Тпг Оисег<Т>::1ппег<(1>::соипе = 7; // (7) сешр1аге<> с1анв Оисег<)эоо1> ( ри)э11с: гешр1асе<гурепате О> с1авв 1ппег ( ргйчасе: всагйс Тпе соипг; // (8) // (9) // (10) // (11) ): згоЫ ргйпс() сопит ( ); гетр1асе<> Тпс Оисег<иоЫ>::сос)е = 12; гешр1абе<> чоЫ Оисег<тгоЫ>::ргйпг() ( вес):: г « "оис Ы>"; ) Эти определения используются поверх обобщенных в точках (4) и (5) для класса Оисег<тгоЫ>, однако другие члены класса Оигег<чоЫ> все еще генерируются из шаблона в точке (1).
Заметим, что после этих объявлений он утрачивает силу в плане обеспечения явной специализации для Оисег<ио ы>. Как и в случае полной специализации шаблона функции, нам нужен способ объявления специализации обычного члена шаблона класса без указания определения (чтобы избежать многократных определений). Хотя для функций-членов и статических данных-членов обычных классов в С++ не разрешены неопределяющие объявления вне класса, последние будут корректны при специализации членов шаблонов классов. Предыдущие определения могут быть обьявлены следующим образом: сешр1асе<> Тпс Оисег<тгоЫ>::соде; сешр1асе<> чо1г) Оигег<чо1д>::ргйпс () Обычные члены сот)е в точке (4) и ргйпс () в точке (5) обобщенного шаблона Оисег (1) имеют единый включающий шаблон класса и, следовательно, требуют одного префикса сешр1асе<> для полной специализации для конкретного набора аргументов шаблона.
224 Глава 12. Специализация и перегрузка Внимательный читатель может заметить, что неопределяющее объявление полной специализации Оигег<чойс)>:: сос)е имеет точно тот же синтаксис, что и при определении с помощью конструктора по умолчанию.
Это действительно так, однако такие объявления всегда интерпретируются как неопределяющие. Таким образом, нет способа определения полной специализации статических данных- членов с типом, который может быть инициализирован с помощью конструктора по умолчанию! с1авв Эебаи1С1п1СОп1у ( рпЫ1с: )Эейап1С1п1СОп1у() ( ) ргйчасе )ЭЕйаи1С1П1СОП1у(ОЕйаи1С1ПТСОП1у СОПВсй) // Копирование запрещено ); Севр1аге<еурепаве Т> с1авв Ясасйсв ( ргйласе: Т вв; ): // Следующий код — это объявление; // для определения синтаксиса не существует с евр1ас е< > Оебап1С1п1СОп1у Яеагйсв<)зейап1с1п1СОп1у>::вв," Шаблон члена Оигег<т>:: 1ппег также можно специализировать для данного аргумента шаблона без влияния на другие члены Оиеег<Т>, для которого специализируется шаблон члена.
И вновь наличие включающего шаблона приводит к необходимости префикса севр1асе<>. В результате получается такой код: севр1асе<> Севр1аее<еурепаве Х> с1авв Оигег<ыс)заг С>::1ппег ( риЫ1с: вгагйс 1опд соипгз // Тип члена изменен севр1асе<> севр1аге<гурепаве Х> 1опд Опеег<ыс)заг С>:: 1ппег<Х>::соиле; Шаблон оасег<т>:: 1ппег также может быль полностью специализированным, однако только лля данного экземпляра Опсег<т>. Теперь нам нужно два префикса севр1асе<>: один из-за включающего класса и один из-за того, что полностью специализируечся (внутренний) шаблон. 225 ) 2.4. Частичная специализация шаблона класса сешр1асе<> сешр1аге<> с1авв Оисег<сЬаг>:: 1ппег<мсЬаг с> ( риЫ1 с: епшп ( соиле = 1 ); // Приведенный далее код некорректен: // сешр1асе<> не может следовать ва // списком параметров шаблона Сешр1асе<сурепаше Х> сетр1асе<> с1авв Оисег<Х>:гйппег<чойс)>; // ОШИБКА! Сравните зто со специализацией шаблона-члена Оисег<Ьоо1>.
Поскодьку он уже полностью специализирован, включаюшего шаблона нет н нам нужен только один префикс сешр1аге<>. сешр1аге<> с1авв Опгег<Ьоо1>:гйппег<мсЬаг С> ( риЫБс: епша ( соиле = 2 ]; 12.4. Частичная специализация шаблона класса Полная специализация шаблона часто полезна„но иногда естественным оказывается желание специализировать шаблон класса для семейства аргументов шаблона, а не только для конкретного набора аргументов. Например, предположим, что у нас есть шаблон класса, реализующий связанный список. Сешр1асе<гурепаше Т> с1авв Ьйвг ( (1) райс: нойс) аррепс)(Т сопвга); Тп11пе айке г 1епдгЬ() сопят) Большой проект с использованием зтого шаблона может инстанцировать свои члены для многих типов.
для невстраиваемых функций-членов (скажем, Бйвс<т>:: аррелс) ( ) ) зто может вызывать заметный рост объектного кода Однако с низкоуровневой точки зрения кол 11вс<1пс*>:: аррепд О и код Ьйвс<чо1д*>:: аррепс) ( ) идентичны. Иными словами, все списки указателей используют одну и ту же реалюацню. Хотя зто нельзя выразить кодом С++, можно приблизиться к цели, отметив, что все списки указателей айве должны инстанцироваться нз другого определенна шаблона 226 Глава 12.
Специализация и перегрузка Семр1ате<гурепате Т> с1авв Ь1вк<Т*> ( рхйчасе: Ьйвс<чоЫ*> Тмр1; а (г) рпЬ11с: чойй аррепс)(Т* р) ( Тмр1.аррепс)(р); вйае.с 1епдсЬ() сопел ( хеспхп 3лпр1.1епдсЬ(); севр1асе<> о1авв Ь1вс<чоЫ*> ( // (3) чоЫ аррепй (чоЫ* р); 1п11пе ваге с 1епдсЬ() сопвс; Этот код вполне корректно работает, поскольку полная специализация предпочтительнее частичной. В результате все функции-члены списков указателей 11вс переадре.
совываются (с помощью встраиваемых функций) реализации списка 1 Твс <чоЫ* >. Это эффективный способ борьбы с так называемым разбуханием кода (в чем часто обвиняют шаблоны С++). Существует ряд ограничений на объявления списков параметров и аргументов частичной специализации. Приведем некоторые из них. 1. Аргументы частичной специализации должны отвечать виду соответствующих па рамстров первичного шаблона (это могуг быть параметры, представляющие собой тип, параметры, не являющиеся типом, илн шаблонные параметры). В этом контексте исходный шаблон в точке (1) называется нервичным шаблоном, а следующее за ним определение — чаоничной слеииализанией (поскольку аргументы шаблона, для которых это определение шаблона должно быть использовано, указаны только частично). Синтаксис, который характеризует частичную специализацию, является комбинацией объявления списка параметров шаблона (семр1асе<...
>) и набора явно указанных аргументов шаблона с именем шаблона класса (в нашем примере это <Т*>). Наш код таит в себе проблему, поскольку 1 Твс<чойс)*> рекурсивно содержит член того же типа 1 1 ел <чо ай*>. Для прерывания рекурсии перед предыдущей частичной специализацией можно ввести полную специализацию. ! 2.4. Частичная специализация шаблона класса 227 2. Список параметров частичной специализации не может иметь аргументов по умолчанию; вместо них используются аргументы по умолчанию первичного шаблона класса. 3. Аргументы частичной специализации, не являющиеся типами, должны быть либо независимыми значениями, либо простыми параметрами, не явлаюшнмися типами.
Они не могут быль сложными зависимымн выражениями наподобие 2*И (где Ы— параметр шаблона). 4. Список аргументов шаблона частичной специализации не должен быть идентичен (без учета переименования) списку параметров первичного шаблона. Приведем пример, иллюстрирующий зти ограничения.
Сешр1асе<сурепаше Т, 1пк 1 = 3> с1авв Я; // Первичный шаблон Сешр1асе<сурепаше Т> с1авв Я<йпс, т>; // ОшиБКА: несоответствие вида // параметра Сешр1аке<курепаше Т = 1пе> с1авв Я<Т, 10>; // ОШИБКА: не разрешены аргументы // по умолчанию сешр1асе<йпс 1> с1авв Я<1пе, 1>2>; // ОШИБКА: не разрешены выражения, // не являющиеся типами Сепр1асе<сурепате П, Тпк К> с1авв Я<0, К>; // ОШИБКА: нет существенных отличий // от первичного шаблона Любая частичная специализация, как и любая полная специализация, связана с первичным шаблоном.
При использовании шаблона сначала всегда ищется первичный шаблон. Кроме того, проверяется соответствие аргументов аргументам связанных специализаций для определения того, какая реализация шаблона должна быть выбрана. Если найлено несколько соответствующих специализаций, из ннх выбирается наиболее специализированная (в смысле, определенном для перегруженных шаблонов функций). Если ни одну из них нельзя назвать "наиболее специализированной", в программе содержится ошибка неоднозначности. Наконец, следует заметить, что частичная специализация шаблона класса вполне может иметь большее количество параметров, чем первичный шаблон.