C как указать в шаблоне тип функции. Функции-шаблоны. Специализированный шаблон - это новый шаблон

Специализация шаблонов является одной из «сложных» фичей языка с++ и использутся в основном при создании библиотек. К сожалению, некоторые особенности специализации шаблонов не очень хорошо раскрыты в популярных книгах по этому языку. Более того, даже 53 страницы официального ISO стандарта языка, посвященные шаблонам, описывают интересные детали сумбурно, оставляя многое на «догадайтесь сами - это же очевидно». Под катом я постарался ясно изложить базовые принципы специализации шаблонов и показать как эти принципы можно использовать в построении магических заклинаний.

Hello World

Как мы привыкли использовать шаблоны? Используем ключевое слово template, затем в угловых скобках имена параметров шаблона , после чего тип и имя. Для параметров также указывают что это такое: тип (typename) или значение (например, int). Тип самого шаблона может быть класс (class), структура (struct - вообщем-то тоже класс) или функция (bool foo() и так далее). Например, простейший шаблонный класс "A" можно задать вот так:

Через некоторое время мы захотим, чтобы наш класс для всех типов работал одинаково, а для какого-нибудь хитрого вроде int - по-другому. Фигня вопрос, пишем специализацию: выглядит так же как объявление но параметры шаблона в угловых скобках не указываем, вместо этого указываем конкретные аргументы шаблона после его имени:

Template<> class A< int > {}; // здесь int - это аргумент шаблона
Готово, можно писать методы и поля специальной реализации для int. Такая специализация обычно называется полной (full specialization или explicit specialization). Для большинства практических задач большего не требуется. А если требуется, то…

Специализированный шаблон - это новый шаблон

Если внимательно читать ISO стандарт С++, то можно обнаружить интересное утверждение: создав специализированный шаблонный класс мы создаем новый шаблонный класс (14.5.4.3). Что это нам дает? Специализированный шаблонный класс может содержать методы, поля или объявления типов которых нет в шаблонном классе который мы специализируем. Удобно, когда нужно чтобы метод шаблонного класса работал только для конкретной специализации - достаточно объявить метод только в этой специализации, остальное сделает компилятор:

Специализированный шаблон может иметь свои параметры шаблона

Дьявол, как известно, в деталях. То, что специализированный шаблонный класс это совсем-совсем новый и отдельный класс конечно интересно, но магии в этом мало. А магия есть в незначительном следствии - если это отдельный шаблонный класс, то он может иметь отдельные, никак не связанные с неспециализированным шаблонным классом параметры (параметры - это то, что после template в угловых скобках). Например, вот так:

Template< typename S, typename U > class A< int > {};
Правда, именно такой код компилятор не скомпилирует - новые параметры шаблона S и U мы никак не используем, что для специализированного шаблонного класса запрещено (а то что это класс специализированный компилятор понимает потому, что у него такое же имя "A" как у уже объявленного шаблонного класса). Компилятор даже специальную ошибку скажет: «explicit specialization is using partial specialization syntax, use template<> instead». Намекает, что если сказать нечего - то надо использовать template<> и не выпендриваться. Тогда для чего же в специализированном шаблонном классе можно использовать новые параметры? Ответ странный - для того, чтобы задать аргументы специализации (аргументы - это то, что после имени класса в угловых скобках). То есть специализируя шаблонный класс мы можем вместо простого и понятного int специализировать его через новые параметры :

Template< typename S, typename U > class A< std::map< S, U > > {};
Такая странная запись скомпилируется. И при использовании получившегося шаблонного класса с std::map будет использована специализация, где тип ключа std::map будет доступен как параметр нового шаблона S, а тип значения std::map как U.

Такая специализация шаблона, при которой задается новый список параметров и через эти параметры задаются аргументы для специализации называется частичной специализацией (partial specialization). Почему «частичной»? Видимо потому, что изначально задумывалась как синтаксис для специализации шаблона не по всем аргументам. Пример, где шаблонный класс с двумя параметрами специализируется только по одному из них (специализация будет работать когда первый аргумент, T, будет указан как int. При этом второй аргумент может быть любым - для этого в частичной специализации введен новый параметр U и указан в списке аргументов для специализации):

Template< typename T, typename S > class B {}; template< typename U > class B< int, U > {};

Магические последствия частичной специализации

Из двух вышеописанных свойств специализации шаблонов есть ряд интересных следствий. Например, при использовании частичной специализации можно, вводя новые параметры шаблона и описывая через них специализированные аргументы, разбивать составные типы на простейшие. В приведенном ниже примере специализированный шаблонный класс A будет использован, если аргументов шаблона является тип указателя на функцию. При этом через новые параметры шаблона S и U можно получить тип возвращаемого значения этой функции и тип ее аргумента:

Template< typename S, typename U > class A< S(*)(U) > {};
А если в специализированном шаблоне объявить typedef или static const int (пользуясь тем, что это новый шаблон), то можно использовать его для извлечения нужной информации из типа. Например, мы используем шаблонный класс для хранения объектов и хотим получить размер переданного объекта или 0, если это указатель. В две строчки:

Template< typename T > struct Get { const static int Size = sizeof(T); }; template< typename S > struct Get< S* > { const static int Size = 0; }; Get< int >::Size // например, 4 Get< int* >::Size // 0 - нашли указатель:)
Магия этого типа используется в основном в библиотеках: stl, boost, loki и так далее. Конечно, при высокоуровневом программировании использовать такие фокусы череповато - думаю, все помнят конструкцию для получения размера массива:). Но в библиотеках частичная специализация позволяет относительно просто реализовывать делегаты, события, сложные контейнеры и прочие иногда очень нужные и полезные вещи.

Коллеги, если найдете ошибку (а я, к сожалению, не гуру - могу ошибаться) или у Вас есть критика, вопросы али дополнения к вышеизложенному - буду рад комментариям.

Update: Обещанное продолжение

Функция-шаблон определяет общий набор операций, который будет применен к данным различ­ных типов. Используя этот механизм, можно применять некоторые общие алгоритмы к широко­му кругу данных. Как известно, многие алгоритмы логически одинаковы вне зависимости от типа данных, с которыми они оперируют. Например, алгоритм быстрой сортировки Quicksort один и тот же и для массива целых чисел, и для массива чисел с плавающей запятой. Отличается только тип данных, подлежащих сортировке. При помощи создания функции-шаблона (generic function) можно определить сущность алгоритма безотносительно к типу данных. После этого компилятор автоматически генерирует корректный код для того типа данных, для которого создается данная конкретная реализация функции на этапе компиляции. По существу, когда создается функция- шаблон, создается функция, которая может автоматически перегружать сама себя.

Функции-шаблоны создаются с использованием ключевого слова template (шаблон). Обычное значение слова «шаблон» достаточно полно отражает его использование в С++. Шаблон исполь­зуется для создания каркаса функции, оставляя компилятору реализацию подробностей. Общая форма функции-шаблона имеет следующий вид:

template возвращаемый_тип имя_функции(список параметров)
{
// тело функции
}

Здесь птип является параметром-типом, «держателем места» (placeholder) для имени типа дан­ных, которое используется функцией. Этот параметр-тип может быть использован в определении функции. Однако это только «держатель места», который будет автоматически заменен компи­лятором на фактический тип данных во время создания конкретной версии функции.

Ниже приведен короткий пример, в котором создается функция-шаблон, имеющая два пара­метра. Эта функция меняет между собой величины значений этих параметров. Поскольку общий процесс обмена значениями между двумя переменными не зависит от их типа, то он естествен­ным способом может быть реализован с помощью функции-шаблона.

// пример шаблона функции
#include
// шаблон функции
template void swap(X &a, X &b)
{
X temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int i=10, j = 20;
float x=10.1, у= 23.3;
char a="x", b="z";
cout << "Original i, j: " << i << " " << j << endl;
cout << "Original x, y: " << x << " " << у << endl;
cout << "Original a, b: " << a << " " << b << endl;
swap(i, j); // обмен целых
swap(x, у); // обмен вещественных значений
swap(a, b); // обмен символов
cout << "Swapped i, j: " << i << " " << j << endl;
cout << "Swapped x, y: " << x << " " << у << endl;
cout << "Swapped a, b: " << a << " " << b << endl;
return 0;
}

Рассмотрим эту программу более внимательно. Строка

Template void swap (X &а, X &b)

Указывает компилятору, что создается шаблон. Здесь X - шаблон типа, используемый в качестве параметра-типа. Далее следует объявление функции swap() с использованием типа данных X для тех параметров, которые будут обмениваться значениями. В функции main() функция swap() вы­зывается с передачей ей данных трех различных типов: целых чисел, чисел с плавающей запятой и символов. Поскольку функция swap() является функцией-шаблоном, то компилятор автоматичес­ки создаст три разные версии функции swap() - одну для работы с целыми числами, другую для работы с числами с плавающей запятой и, наконец, третью для работы с переменными символь­ного типа.

10.1. Определение шаблона функции

Иногда может показаться, что сильно типизированный язык создает препятствия для реализации совсем простых функций. Например, хотя следующий алгоритм функции min() тривиален, сильная типизация требует, чтобы его разновидности были реализованы для всех типов, которые мы собираемся сравнивать:

int min(int a, int b) {

return a b ? a: b;

double min(double a, double b) {

return a b ? a: b;

Заманчивую альтернативу явному определению каждого экземпляра функции min() представляет использование макросов, расширяемых препроцессором:

#define min(a, b) ((a) (b) ? (a) : (b))

Но этот подход таит в себе потенциальную опасность. Определенный выше макрос правильно работает при простых обращениях к min(), например:

min(10.0, 20.0);

но может преподнести сюрпризы в более сложных случаях: такой механизм ведет себя не как вызов функции, он лишь выполняет текстовую подстановку аргументов. В результате значения обоих аргументов оцениваются дважды: один раз при сравнении a и b, а второй – при вычислении возвращаемого макросом результата:

#include iostream

#define min(a,b) ((a) (b) ? (a) : (b))

const int size = 10;

while (min(p++,ia) != ia)

cout "elem_cnt: " elem_cnt

" expecting: " size endl;

На первый взгляд, эта программа подсчитывает количество элементов в массиве ia целых чисел. Но в этом случае макрос min() расширяется неверно, поскольку операция постинкремента применяется к аргументу-указателю дважды при каждой подстановке. В результате программа печатает строку, свидетельствующую о неправильных вычислениях:

elem_cnt: 5 expecting: 10

Шаблоны функций предоставляют в наше распоряжение механизм, с помощью которого можно сохранить семантику определений и вызовов функций (инкапсуляция фрагмента кода в одном месте программы и гарантированно однократное вычисление аргументов), не принося в жертву сильную типизацию языка C++, как в случае применения макросов.

Шаблон дает алгоритм, используемый для автоматической генерации экземпляров функций с различными типами. Программист параметризует все или только некоторые типы в интерфейсе функции (т.е. типы формальных параметров и возвращаемого значения), оставляя ее тело неизменным. Функция хорошо подходит на роль шаблона, если ее реализация остается инвариантной на некотором множестве экземпляров, различающихся типами данных, как, скажем, в случае min().

Так определяется шаблон функции min():

template class Type

Type min2(Type a, Type b) {

return a b ? a: b;

// правильно: min(int, int);

// правильно: min(double, double);

min(10.0, 20.0);

Если вместо макроса препроцессора min() подставить в текст предыдущей программы этот шаблон, то результат будет правильным:

elem_cnt: 10 expecting: 10

(В стандартной библиотеке C++ есть шаблоны функций для многих часто используемых алгоритмов, например для min(). Эти алгоритмы описываются в главе 12. А в данной вводной главе мы приводим собственные упрощенные версии некоторых алгоритмов из стандартной библиотеки.)

Как объявление, так и определение шаблона функции всегда должны начинаться с ключевого слова template, за которым следует список разделенных запятыми идентификаторов, заключенный в угловые скобки " и ", – список параметров шаблона, обязательно непустой. У шаблона могут быть параметры-типы, представляющие некоторый тип, и параметры-константы, представляющие фиксированное константное выражение.

Параметр-тип состоит из ключевого слова class или ключевого слова typename, за которым следует идентификатор. Эти слова всегда обозначают, что последующее имя относится к встроенному или определенному пользователем типу. Имя параметра шаблона выбирает программист. В приведенном примере мы использовали имя Type, но могли выбрать и любое другое:

template class Glorp

Glorp min2(Glorp a, Glorp b) {

return a b ? a: b;

При конкретизации (порождении конкретного экземпляра) шаблона вместо параметра-типа подставляется фактический встроенный или определенный пользователем тип. Любой из типов int, double, char*, vectorint или listdouble является допустимым аргументом шаблона.

Параметр-константа выглядит как обычное объявление. Он говорит о том, что вместо имени параметра должно быть подставлено значение константы из определения шаблона. Например, size – это параметр-константа, который представляет размер массива arr:

template class Type, int size

Type min(Type (arr) );

Вслед за списком параметров шаблона идет объявление или определение функции. Если не обращать внимания на присутствие параметров в виде спецификаторов типа или констант, то определение шаблона функции выглядит точно так же, как и для обычных функций:

template class Type, int size

/* параметризованная функция для отыскания

* минимального значения в массиве */

Type min_val = r_array;

for (int i = 1; i size; ++i)

if (r_array[i] min_val)

min_val = r_array[i];

В этом примере Type определяет тип значения, возвращаемого функцией min(), тип параметра r_array и тип локальной переменной min_val; size задает размер массива r_array. В ходе работы программы при использовании функции min() вместо Type могут быть подставлены любые встроенные и определенные пользователем типы, а вместо size – те или иные константные выражения. (Напомним, что работать с функцией можно двояко: вызвать ее или взять ее адрес).

Процесс подстановки типов и значений вместо параметров называется конкретизацией шаблона. (Подробнее мы остановимся на этом в следующем разделе.)

Список параметров нашей функции min() может показаться чересчур коротким. Как было сказано в разделе 7.3, когда параметром является массив, передается указатель на его первый элемент, первая же размерность фактического аргумента-массива внутри определения функции неизвестна. Чтобы обойти эту трудность, мы объявили первый параметр min() как ссылку на массив, а второй – как его размер. Недостаток подобного подхода в том, что при использовании шаблона с массивами одного и того же типа int, но разных размеров генерируются (или конкретизируются) различные экземпляры функции min().

Имя параметра разрешено употреблять внутри объявления или определения шаблона. Параметр-тип служит спецификатором типа; его можно использовать точно так же, как спецификатор любого встроенного или пользовательского типа, например в объявлении переменных или в операциях приведения типов. Параметр-константа применяется как константное значение – там, где требуются константные выражения, например для задания размера в объявлении массива или в качестве начального значения элемента перечисления.

// size определяет размер параметра-массива и инициализирует

// переменную типа const int

template class Type, int size

Type min(const Type (r_array))

const int loc_size = size;

Type loc_array;

Если в глобальной области видимости объявлен объект, функция или тип с тем же именем, что у параметра шаблона, то глобальное имя оказывается скрытым. В следующем примере тип переменной tmp не double, а тот, что у параметра шаблона Type:

typedef double Type;

template class Type

Type min(Type a, Type b)

// tmp имеет тот же тип, что параметр шаблона Type, а не заданный

// глобальным typedef

Type tm = a b ? a: b;

Объект или тип, объявленные внутри определения шаблона функции, не могут иметь то же имя, что и какой-то из параметров:

template class Type

Type min(Type a, Type b)

// ошибка: повторное объявление имени Type, совпадающего с именем

// параметра шаблона

typedef double Type;

Type tmp = a b ? a: b;

Имя параметра-типа шаблона можно использовать для задания типа возвращаемого значения:

// правильно: T1 представляет тип значения, возвращаемого min(),

// а T2 и T3 – параметры-типы этой функции

template class T1, class T2, class T3

В одном списке параметров некоторое имя разрешается употреблять только один раз. Например, следующее определение будет помечено как ошибка компиляции:

// ошибка: неправильное повторное использование имени параметра Type

template class Type, class Type

Type min(Type, Type);

Однако одно и то же имя можно многократно применять внутри объявления или определения шаблона:

// правильно: повторное использование имени Type внутри шаблона

template class Type

Type min(Type, Type);

template class Type

Type max(Type, Type);

Имена параметров в объявлении и определении не обязаны совпадать. Так, все три объявления min() относятся к одному и тому же шаблону функции:

// все три объявления min() относятся к одному и тому же шаблону функции

// опережающие объявления шаблона

template class T T min(T, T);

template class U U min(U, U);

// фактическое определение шаблона

template class Type

Type min(Type a, Type b) { /* ... */ }

Количество появлений одного и того же параметра шаблона в списке параметров функции не ограничено. В следующем примере Type используется для представления двух разных параметров:

// правильно: Type используется неоднократно в списке параметров шаблона

template class Type

Type sum(const vectorType , Type);

Если шаблон функции имеет несколько параметров-типов, то каждому из них должно предшествовать ключевое слово class или typename:

// правильно: ключевые слова typename и class могут перемежаться

template typename T, class U

// ошибка: должно быть typename T, class U или

// typename T, typename U

template typename T, U

В списке параметров шаблона функции ключевые слова typename и class имеют одинаковый смысл и, следовательно, взаимозаменяемы. Любое из них может использоваться для объявления разных параметров-типов шаблона в одном и том же списке (как было продемонстрировано на примере шаблона функции minus()). Для обозначения параметра-типа более естественно, на первый взгляд, употреблять ключевое слово typename, а не class, ведь оно ясно указывает, что за ним следует имя типа. Однако это слово было добавлено в язык лишь недавно, как часть стандарта C++, поэтому в старых программах вы скорее всего встретите слово class. (Не говоря уже о том, что class короче, чем typename, а человек по природе своей ленив.)

Ключевое слово typename упрощает разбор определений шаблонов. (Мы лишь кратко остановимся на том, зачем оно понадобилось. Желающим узнать об этом подробнее рекомендуем обратиться к книге Страуструпа “Design and Evolution of C++”.)

При таком разборе компилятор должен отличать выражения-типы от тех, которые таковыми не являются; выявить это не всегда возможно. Например, если компилятор встречает в определении шаблона выражение Parm::name и если Parm – это параметр-тип, представляющий класс, то следует ли считать, что name представляет член-тип класса Parm?

template class Parm, class U

Parm::name * p; // это объявление указателя или умножение?

// На самом деле умножение

Компилятор не знает, является ли name типом, поскольку определение класса, представленного параметром Parm, недоступно до момента конкретизации шаблона. Чтобы такое определение шаблона можно было разобрать, пользователь должен подсказать компилятору, какие выражения включают типы. Для этого служит ключевое слово typename. Например, если мы хотим, чтобы выражение Parm::name в шаблоне функции minus() было именем типа и, следовательно, вся строка трактовалась как объявление указателя, то нужно модифицировать текст следующим образом:

template class Parm, class U

Parm minus(Parm* array, U value)

typename Parm::name * p; // теперь это объявление указателя

Ключевое слово typename используется также в списке параметров шаблона для указания того, что параметр является типом.

Шаблон функции можно объявлять как inline или extern – как и обычную функцию. Спецификатор помещается после списка параметров, а не перед словом template.

// правильно: спецификатор после списка параметров

template typename Type

Type min(Type, Type);

// ошибка: спецификатор inline не на месте

template typename Type

Type min(ArrayType, int);

Упражнение 10.1

Определите, какие из данных определений шаблонов функций неправильны. Исправьте ошибки.

(a) template class T, U, class V

void foo(T, U, V);

(b) template class T

(c) template class T1, typename T2, class T3

(d) inline template typename T

T foo(T, unsigned int*);

(e) template class myT, class myT

void foo(myT, myT);

(f) template class T

(g) typedef char Ctype;

template class Ctype

Ctype foo(Ctype a, Ctype b);

Упражнение 10.2

Какие из повторных объявлений шаблонов ошибочны? Почему?

(a) template class Type

Type bar(Type, Type);

template class Type

Type bar(Type, Type);

(b) template class T1, class T2

void bar(T1, T2);

template typename C1, typename C2

void bar(C1, C2);

Упражнение 10.3

Перепишите функцию putValues() из раздела 7.3.3 в виде шаблона. Параметризуйте его так, чтобы было два параметра шаблона (для типа элементов массива и для размера массива) и один параметр функции, являющийся ссылкой на массив. Напишите определение шаблона функции.

Из книги Microsoft Office автора Леонтьев Виталий Петрович

Выбор шаблона Как мы уже говорили, Publisher рассчитан на работу в «пошаговом» режиме – мы как бы собираем будущую публикацию по кусочкам. А еще точнее – создаем ее на основе одного из бесчисленных шаблонов. На компакт-диске с Publisher хранится более полутора тысяч шаблонов

Из книги Справочное руководство по C++ автора Страустрап Бьярн

R.7.1.4 Спецификация шаблона типа Спецификация шаблона типа используется для задания семейства типов или функций (см.

Из книги Эффективное делопроизводство автора Пташинский Владимир Сергеевич

Понятие шаблона Для упрощения работы по созданию и форматированию текстов, стандартизации расположения и оформления текста, графики, типизации операций обработки документов и прочего используются шаблоны документов. Пакет Microsoft Office дает различные определения шаблона

Из книги Обработка баз данных на Visual Basic®.NET автора Мак-Манус Джеффри П

Из книги Создание шаблонов Joomla автора Автор неизвестен

Структура директорий шаблона Теперь необходимо позаботится о кое-каких условиях. Как уже говорилось, шаблон должен иметь определенную структуру директорий:[ПутьКJoomla!]/templates/[НазваниеШаблона]/[ПутьКJoomla!]/templates/[ НазваниеШаблона]/css/[ПутьКJoomla!]/templates/[

Из книги XSLT автора Хольцнер Стивен

Структура шаблона Помимо специального заголовка, для шаблона необходима структура. Создать структуру можно при помощи таблиц или тегов

. Далее мы опишем создание табличной версии структуры. Если в Dremweaver все еще активирован режим разметки (layout mode), закройте

Из книги Технология XSLT автора Валиков Алексей Николаевич

Создание шаблона В главе 2 для выбора узлов в planets.xml и преобразования этого документа в HTML я создал основной шаблон. Шаблоны в таблицах стилей создаются при помощи элементов , задающих правила для требуемых преобразований. Мы создали шаблон, находивший корневой

Из книги Язык программирования Си для персонального компьютера автора Бочков C. О.

Тело шаблона Фактически, элемент xsl:template, определяющий шаблонное правило, задает не более чем условия, при которых это правило должно выполняться. Конкретные же действия и инструкции, которые должны быть исполнены, определяются содержимым элемента xsl:template и составляют

Из книги Язык Си - руководство для начинающих автора Прата Стивен

Определение функции Определение функции специфицирует имя, формальные параметры и тело функции. Оно может также специфицировать тип возвращаемого значения и класс памяти функции. Синтаксис определения функции следующий:[<спецификация КП>][<спецификация

Из книги Недокументированные и малоизвестные возможности Windows XP автора Клименко Роман Александрович

Определение функции с аргументом: формальные аргументы Определение нашей функции начинается с двух строк: space(number)int number;Первая строка информирует компилятор о том, что у функции space() имеется аргумент и что его имя number. Вторая строка - описание, указывающее

Из книги Как сделать свой сайт и заработать на нем. Практическое пособие для начинающих по заработку в Интернете автора Мухутдинов Евгений

Создание шаблона безопасности Чтобы создать шаблон безопасности на основе любого другого шаблона, необходимо в контекстном меню шаблона выбрать команду Сохранить как. Затем консоль управления Microsoft предложит вам указать имя нового шаблона, после чего он отобразится в

Из книги C++ для начинающих автора Липпман Стенли

Из книги автора

10.2. Конкретизация шаблона функции Шаблон функции описывает, как следует строить конкретные функции, если задано множество фактических типов или значений. Процесс конструирования называется конкретизацией шаблона. Выполняется он неявно, как побочный эффект вызова

Из книги автора

Из книги автора

10.11. Пример шаблона функции В этом разделе приводится пример, показывающий, как можно определять и использовать шаблоны функций. Здесь определяется шаблон sort(), который затем применяется для сортировки элементов массива. Сам массив представлен шаблоном класса Array (см.

Из книги автора

16.1. Определение шаблона класса Предположим, что нам нужно определить класс, поддерживающий механизм очереди. Очередь - это структура данных для хранения коллекции объектов; они помещаются в конец очереди, а извлекаются из ее начала. Поведение очереди описывают

До настоящего момента обсуждались вопросы построения шаблонных классов на основе предопределенных (включенных в состав библиотеки классов) шаблонов классов. В этом разделе на простом примере обсуждается техника объявления собственных шаблонов классов и шаблонов функций.

Шаблонный класс обеспечивает стандартную реализацию дополнительной функциональности на основе ранее объявленных подстановочных классов.

Эта дополнительная функциональность может накладывать дополнительные ограничения на подстановочный класс . Например, для успешной работы объекта шаблонного класса подстановочный класс должен наследовать определенному интерфейсу. Иначе функциональность шаблонного класса просто невозможно будет реализовать.

Для формирования ограничений на подстановочные классы в C# используется механизм ограничителей параметров шаблона - он вводится при объявлении шаблона с помощью ключевого слова where , за которым могут располагаться имя параметра типа и список типов класса или интерфейса либо конструктор – ограничение new() :

Using System; using System.Collections; using System.Collections.Generic; namespace PatternArrays { //========== Это заголовок шаблона класса W ========== // Шаблон класса своими руками. T – параметр шаблона. // Шаблонный класс – это класс-шаблон, который детализируется // подстановочным классом. // При создании шаблонного класса вхождения параметра шаблона // (в данном случае это T) замещаются именем подстановочного // класса. Разработчик шаблона класса может выдвигать требования // относительно характеристик подстановочного класса. // Для этого используются специальные языковые конструкции, // называемые ОГРАНИЧИТЕЛЯМИ ПАРАМЕТРА ШАБЛОНА. // ОГРАНИЧИТЕЛЬ ПАРАМЕТРА ШАБЛОНА формулирует требования для // подстановочного класса. class W where T: IComparable, new() // Ограничитель параметра шаблона new() – особый // ограничитель. // Во-первых, в списке ограничителей шаблона // он всегда последний. // Во-вторых, этот ограничитель НЕ ограничивает. // Он ОБЕСПЕЧИВАЕТ обязательное выполнение явно // заданного конструктора умолчания для // подстановочного класса в шаблонном // классе. Это единственный способ заставить // выполниться конструктор умолчания // подстановочного класса при создании // объекта шаблонного класса. // Ограничитель подстановочного класса. // Шаблонный класс строится на основе шаблона и множества // подстановочных классов, которыми замещаются параметры // шаблона. Таким образом ограничители подстановочного // класса формулируют требования по поводу "родословной" // подстановочного класса. // В данном случае претендент на замещение параметра T // в шаблоне W обязательно должен наследовать интерфейс // IComparable. { // Вот место, которое предназначено объекту подстановочного класса. // Объект - представитель шаблонного класса включает объект, // представляющий подстановочный класс. public T t; // Конструктор шаблона. // Вот по какой схеме производится встраивание объекта-представителя // подстановочного класса. Всего лишь для того, чтобы эта схема // построения объекта - представителя шаблонного класса работала, // в объявлении шаблона должен присутствовать ограничитель параметра // шаблона new(). Его отсутствие приводит к возникновению ошибки // компиляции. С каких это пор необходимое требование стали // называть ограничением? public W() { t = new T(); } // Сравнение объектов в шаблоне. Обращение к функциям сравнения // регламентировано стандартными интерфейсами. // Полиморфизм через интерфейсы в действии. public int wCompare(T t) { return ((IComparable)this.t).CompareTo(t); } // А вот замечательный шаблон функции. // Он реализован в рамках класса-шаблона W. // Эта функция предназначена для формирования шаблонных очередей // из входных массивов объектов - представителей подстановочного // класса, представленного параметром шаблона Z. // Между прочим, такое обозначение параметра ничуть не хуже любого // другого. Более того, если бы здесь было использовано старое // обозначение параметра, транслятор выступил бы с предупреждением // по поводу того, что две разных сущности (параметр шаблона для // шаблона класса и параметр шаблона для параметра функции) // в рамках одного и того же объявления имеют одинаковые обозначения. public void QueueFormer(Queue queue, params Z values) { foreach (Z z in values) { queue.Enqueue(z); } } } //=============================================================== // Вот классы-кандидаты на подстановку в шаблон. // Первый класс подходит, а второй – не подходит! // Все решается на этапе трансляции. //=============================================================== class xPoints: IComparable { // Объект-генератор "случайных" чисел. static Random rnd = new Random(); public int x; public int y; public xPoints() { x = rnd.Next(0, 100); y = rnd.Next(0, 100); } // Ничто не может помешать классу иметь // различные версии конструкторов! public xPoints(int x, int y) { this.x = x; this.y = y; } // Вычисляется расстояние от начала координат. public int R { get { return (int)(Math.Sqrt(x * x + y * y)); } } // После реализации соответствующего интерфейса объект-КОМПАРЕР // обеспечивает реализацию алгоритма сравнения. public int CompareTo(object p) { return (this.R - ((xPoints)p).R); } } class yPoints { // Объект-генератор "случайных" чисел. static Random rnd = new Random(); public int x; public int y; public yPoints() { x = rnd.Next(0, 100); y = rnd.Next(0, 100); } // Шаблон функции в рамках объявления "обычного" класса. // Функция предназначена для формирования шаблонных магазинов // из входных массивов объектов - представителей подстановочного // класса, представленного параметром шаблона T. public void StackFormer(Stack stack, params T values) { foreach (T t in values) { stack.Push(t); } } } //============================================================== class Class1 { static void Main(string args) { W xw0 = new W(); W xw1 = new W(); // Объекты - представители шаблонного класса можно сравнивать // в результате // реализации интерфейса IComparable. if (xw0.wCompare(xw1.t) == 0) Console.WriteLine("Yes"); else Console.WriteLine("No"); // В силу ограничений параметра шаблона T, следующий код // для подстановочного // класса в принципе нереализуем.============================= //W yw0 = new W(); //W yw1 = new W(); //if (yw0.ww(yw1.t) == 0) Console.WriteLine("Yes"); //else Console.WriteLine("No"); //===================================================== // Демонстрация использования шаблона функции. // На основе подстановочного класса сформировали // шаблонную функцию для подстановочного класса, // которая обслуживает шаблонные очереди, формируемые на основе // предопределенного шаблона класса Queue<...>. Queue xpQueue = new Queue(); xw1.QueueFormer(xpQueue, new xPoints(0, 9), new xPoints(1, 8), new xPoints(2, 7), new xPoints(3, 6), new xPoints(4, 5), new xPoints(5, 4), new xPoints(6, 3), new xPoints(7, 2), new xPoints(8, 1), new xPoints(9, 0)); // Шаблоны классов и шаблоны функций концептуально не связаны. // В C# это самостоятельные и независимые конструкции. // Шаблон функции может быть объявлен где угодно - // в шаблоне класса и в рамках объявления "обычного" класса. // При объявлении шаблона функции ограничения на свойства // подстановочного класса, представленного параметром шаблона, // не упоминаются. // В силу ограничений на параметр шаблона класс yPoints в принципе // не может быть использован для построения шаблонного класса // на основе шаблона class W. // Однако этот же самый класс может быть использован для построения // шаблонной функции в шаблонном классе, созданном на основе // подстановочного класса xPoints! Queue ypQueue = new Queue(); xw1.QueueFormer(ypQueue, new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints()); // А вот применение шаблона функции, объявленного в "обычном" классе. // Создали объект класса, содержащего шаблон функции // по обслуживанию очередей. yPoints yp = new yPoints(); // Ссоздали шаблонный стек и воспользовались шаблоном функции, // объявленной yPoints в классе. Stack xpStack = new Stack(); yp.StackFormer(xpStack, new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints()); } } } Листинг 13.2.

Пример использования шаблонов: сортировка

Старая задача, новые решения с использованием предопределенных шаблонов классов и интерфейсов...

Using System; using System.Collections; using System.Collections.Generic; namespace PatternArrays { // Данные для массива элементов. // Подлежат сортировке в составе шаблонного массива методом Sort. class Points { public int x; public int y; public Points(int key1, int key2) { x = key1; y = key2; } // Вычисляется расстояние от начала координат. public int R { get { return (int)(Math.Sqrt(x * x + y * y)); } } } // ...ШАБЛОННЫЙ КОМПАРЕР на основе шаблона интерфейса... class myComparer: IComparer { // Предлагаемый ШАБЛОННЫЙ метод сравнения возвращает разность расстояний // двух точек (вычисляется по теореме Пифагора) от начала координат // – точки с координатами (0,0). Чем ближе точки к началу координат // – тем они меньше. Не требуется никаких явных приведений типа. // Шаблон настроен на работу с классом Points. int IComparerCompare(Points p1, Points p2) { return (p1.R - p2.R); } } // После реализации соответствующего ШАБЛОННОГО интерфейса // объект-КОМПАРЕР обеспечивает реализацию стандартного алгоритма // сортировки. class Class1 { static void Main(string args) { // Объект-генератор "случайных" чисел. Random rnd = new Random(); int i; // Очередь Points. QueueQP = new Queue(); // Шаблонный перечислитель. Предназначен для обслуживания // шаблонной очереди элементов класса Points. IEnumeratorEnP; // Сортировка поддерживается классом Array. Points pp; // Создали Компарер, способный сравнивать пары // объектов - представителей класса Points. myComparer c = new myComparer(); Console.WriteLine("========================================"); // Проинициализировали массив объектов - представителей класса Points. for (i = 0; i < 10; i++) { qP.Enqueue(new Points(rnd.Next(0, 10), rnd.Next(0, 10))); } enP = ((IEnumerable)(qP)).GetEnumerator(); for (i = 0; enP.MoveNext(); i++) { Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y); } // Сортируются элементы массива типа Points, который формируется на // основе шаблонной очереди. // Условием успешной сортировки элементов массива является // реализация интерфейса IComparer. Если Компарер не сумеет // справиться с поставленной задачей – будет возбуждено исключение. // На основе очереди построили массив. pp = qP.ToArray(); // А саму очередь можно почистить! qP.Clear(); try { Array.Sort(pp, c); } catch (Exception ex) { Console.WriteLine(ex); } // Сортировка произведена, очередь восстановлена. for (i = 0; i < 10; i++) qP.Enqueue(pp[i]); Console.WriteLine("========================================"); enP = ((IEnumerable)(qP)).GetEnumerator(); for (i = 0; enP.MoveNext(); i++) { Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y); } Console.WriteLine("========================================"); } } } Листинг 13.3.

Nullable-типы

Nullable-типы (простые Nullable-типы) представляют собой расширения простых типов. Их объявления принадлежат пространству имен System.Nullable .

Это шаблонные типы, то есть типы, построенные в результате детализации шаблонов. Шаблон Nullable<> используется для расширения простых типов, которые по своей сути являются структурами. Для обозначения Nullable шаблонных (построенных на основе шаблона) типов используются две нотации.

Собрался я было писать текст про всякие крутые структуры данных и тут оказалось, что мы ещё не разбирали несколько очень важных возможностей C++. Шаблоны - одна из них.

Шаблоны (templates) - очень мощное средство. Шаблонные функции и классы позволяют очень сильно упростить программисту жизнь и сберечь огромное количество времени, сил и нервов. Если вам покажется, что шаблоны не сильно-то и значимая тема для изучения, знайте - вы заблуждаетесь.

Шаблонные функции

Простой пример шаблонной функции:

код на языке c++ Type square (Type a) { Type b; b = a*a; return b; } int x = 5; int i; i = square(5); float y = 0.5; float f; f = square(y);

Если бы мы создавали функции по старинке, то тогда бы пришлось писать две разные функции: для типа int и для типа float. А если бы понадобилась такая же функция, использующая другие типы, пришлось бы заново писать и её. Используя шаблоны, можно ограничиться только одним экземпляром функции, оставив всю грязную работу компилятору.

Вместо использования какого-то определённого типа, в функции используется параметрический тип (или по другому - аргумент шаблона). Здесь я обозвал параметрический тип идентификатором Type. В функции этот идентификатор встречается три раза: возвращаемое значение, аргумент функции и определение переменной s. То есть Type используется как любой обычный тип.

Но чтобы код заработал, перед функцией нужно добавить следующую строку (я показал несколько вариантов синтаксиса, все они рабочие):

код на языке c++ template Type square (Type a) template < class Type > Type square (Type a) template < class Type > Type square (Type a)

Итак, перед функцией должно стоять ключевое слово template (шаблон), а в угловых скобках нужно указать имя параметрического типа с ключевым словом class. Вместо ключевого слова class можно использовать type - в общем-то никакой разницы.

Идентификатор параметрического типа тоже может быть любым. Мы часто будем пользоваться вот такими: TypeA, TypeB, Datatype, T.

Важное замечание : У шаблонных функций должен быть аргумент, чтобы компилятор мог определить какой именно тип использовать.

В шаблонах можно использовать несколько параметрических типов, и конечно же можно смешивать параметрические типы со стандартными (только нужно позаботиться о правильном приведении типов). Приведу пример в котором используется два параметрических типа TypeA, TypeB и базовый тип int:

код на языке c++ template TypeB example_function (TypeA a, TypeB b) { int x = 5; b = a + x; return b; }

Но шаблонные функции - не самое интересное, что мы сегодня рассмотрим.

Шаблонные классы

В общем-то шаблонные классы создаются почти так же как и шаблонные функции - перед именем класса записывается ключевое слово template. Шаблонные классы рассмотрим на примере стека:

код на языке c++ template class stack { private: int top; Type s; public: stack (): top(0) {} void push(Type var) { top++; s = var; } Type pop(); }; template Type stack::pop() { Type var = s; top--; return var; } Здесь мы опре

делили стек из десяти элементов. Эти элементы могут быть какого угодно типа, об этом чуть-чуть ниже.

Единственное на что хочу обратить ваше внимание: определение функций push и pop. Функция push определена внутри класса, а функция pop - снаружи. Для всех функции объявлённых за пределами класса, нужно обязательно указывать ключевое слово template. Выражение перед именем функции совпадает с тем, которое указывается перед именем класса.

Теперь посмотрим как работать с шаблонными классами:

код на языке c++ stack s1; stack s2; s1.push(3); s1.push(2); s1.pop(); s2.push(0.5);

При создании объекта, после имени класса нужно поставить угловые скобки, в которых указать нужный тип. После этого объекты используются так, как мы привыкли.

У шаблонных классов есть одна потрясающая особенность - кроме стандартных типов, они могут работать и с пользовательскими. Рассмотрим небольшой пример. Для этого определим простой класс warrior:

код на языке c++ class warrior { public: int health; warrior () : health(0) {} }; stack s; warrior w1; warrior w2; warrior w3; s.push(w1); s.push(w3); s.pop(); s.push(w2);

Смотрите, теперь в стеках можно размещать переменные типа warrior!!! Возможно вы не поверите мне, но это очень круто! Насколько это круто, вы сможете убедиться когда на основе списков мы будем создавать графы и деревья.

По шаблонам пока всё. Позже разберём более сложные случаи использования шаблонных классов.



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: