Создание объектов по шаблонам

Определение шаблонов классов

Переопределение шаблонов

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

В качестве примера можно определить новую версию функции Мах, которая будет работать с экземплярами класса CCurrency, описанного в параграфе «Перегрузка операторов» гл. 5. Вспомним: объект класса CCurrency хранит денежную сумму в виде числа долларов и числа центов. Очевидно, что код, определенный в шаблоне Мах, не подходит для сравнения денежных величин, хранимых в двух объектах CCurrency. Следовательно, приведенную ниже версию функции Мах можно включить в программу дополнительно к уже имеющемуся шаблону Мах.

CCurrency Max (CCurrency A, CCurrency В) { long DollarsA, DollarsB; int CentsA, CentsB; A.GetAmount (SDollarsA, SCentsA); B.GetAmount UDollarsB, &CentsB); if (DollarsA > DollarsB || DollarsA == DollarsB && CentsA > CentsB) return A; else return B; }

Если после этого программа вызовет функцию Мах, передав ей два объекта класса CCurrency, компилятор инициирует вызов приведенной выше функции вместо создания экземпляра функции по шаблону Мах. Например:

CCurrency Bucks1 (29, 95); CCurrency Bucks2 (31, 47); Max (Bucks1, Bucks2}.PrintAmount ();

Результатом будет

$31.47

Совет

Вместо переопределения функции Мах для сравнения двух объектов CCurrency можно перегрузить оператор ">", чтобы корректно сравнивать значения денежных сумм, сохраняемых в этих двух объектах. Тогда приведенный в примере шаблон Мах, использующий оператор ">", будет корректно обрабатывать объекты класса CCurrency. Описание перегруженных операторов см. в гл. 5.

В гл. 4 показано, как создание нового производного класса из существующего позволяет повторно использовать код, избегая при этом ненужного дублирования. Допустим, вы сконструировали класс для хранения списка 100 целочисленных значений. Базовое определение класса может быть следующим.

class IntList { public: IntList (); int Setltem (int Index, const int &Item); int Getltem (int Index, int &Item); private: int Buffer [100];};

Целочисленные значения хранятся в закрытом массиве Buffer, а специально написанный конструктор по умолчанию инициализирует нулями все элементы этого массива. Функции-члены SetItem и GetItem используются для присваивания или получения значений указанных элементов.

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

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

template <class T, int I> class CList { public: int SetItem (int Index, const Т &Item); int GetItem (int Index, Т &Item); private: Т Buffer [I]; };

В этом определении T является параметром типа, а I – параметром-константой (точнее, в данном примере – это параметр-константа типа int). Как вы вскоре увидите, фактические значения для параметров T и I устанавливаются при создании определенного экземпляра класса. В его шаблон класса можно включить список из любого числа параметров, ограниченный символами "<" ">" (список должен содержать хотя бы один параметр). Параметры-константы могут иметь любой допустимый тип (не обязательно int, как в приведенном выше примере). Внутри определения класса параметр типа может находиться в любом месте программы, в котором допустимо использование спецификации типа, а параметр-константа – в любом месте программы, в котором допустимо применение константного выражения описанного типа (в нашем примере int).

Функцию-член SetItem можно определить следующим образом.

template<class Т,int I> int CList<T,I>::Setltem(int Index,const Т &Item) { if (Index < 0 || Index > I - 1) return 0; // ошибка Buffer [Index] = Item; return 1; // успешное завершение}

Обратите внимание: функция возвращает значение 1 при удачном завершении или 0, если заданное значение индекса недопустимо.

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

1. Определение должно начинаться спецификатором template, за которым следует такой же список параметров в угловых скобках, как и в определении шаблона класса (в приведенном примере template <class Т, int I>).

2. За именем класса, предшествующим операции расширения области видимости, должен следовать список имен параметров шаблона (в нашем примере – CList <T, I>). Список используется для полного определения типа класса, к которому принадлежит функция.

Совет

Функция-член шаблона может быть реализована вне его определения. Однако эта реализация должна быть включена в каждый исходный файл программы, содержащий вызов функции. Тогда компилятор сможет сгенерировать код функции из ее определения. В программу с несколькими исходными файлами можно ввести как определение шаблона, так и определения всех его методов в одном файле заголовков, входящем во все исходные файлы. Для функций-членов шаблона класса включение определения функции в несколько исходных файлов не приводит к возникновению ошибки компоновщика "multiply definition symbol" (многократное определение символического имени).

По аналогии метод Getitem можно определить следующим образом.

template <class Т, int I> int CList <T, I>::GetItem (int Index, Т &Item) { if (Index < 0 || Index > I - 1) return 0; Item = Buffer [Index]; return 1; }

Примечание

В определении шаблона класса идентификатор i может использоваться как значение размерности массива, поскольку он является параметром-константой шаблона, а не обычным параметром или переменной, которые нельзя использовать для задания размерности массива (язык C++ не позволяет динамически изменять размерность массива во время выполнения программы), Использование параметра-константы допустимо на этапе компиляции, так как при запуске программы значение этого параметра становится константой.

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

CList <int, 100> IntList;

Это объявление описывает IntList как экземпляр версии CList, в которой каждое вхождение параметра т заменяется типом int, а каждое вхождение параметра I — константой 100. В результате, в полученном объекте элемент Buffer будет определен как массив из 100 целочисленных значений типа int, а функциям-членам SetItem и GetItem будут переданы ссылки на значения типа int (в соответствии со вторым параметром).

Обратите внимание: согласно списку параметров в определении шаблона CList

<class Т, int I>

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

После создания объекта и задания всех типов в списке параметров, компилятор автоматически обеспечивает работу с этими типами при последующем обращении к переменным-членам или вызове функций-членов класса. Таким образом, в объекте intList в метод SetItem нужно передать два значения типа int.

IntList.Setltem(0,5); //первому элементу списка присваивается целое //число

Чтобы создать объект для хранения списка строк, экземпляр CList можно определить так.

CList <char *, 25> StringList;

Тогда для присваивания строки какому-либо элементу списка следует в функции Setltem передать второму параметру указатель на массив типа char.

StringList.Setltem (0, "Mike"); // присваивает строку первому // элементу списка

Объект для хранения списка значений типа double создается следующим образом.

CList «double, 25> *DoubleList; DoubleList = new CList <double, 25>;

Обратите внимание: при определении типа указателя DoubleList, так же как и при задании типа в операторе new, вместе с именем шаблона следует включить список его параметров <double, 25>. Список параметров шаблона является неотъемлемой частью определения типа. Одного имени шаблона недостаточно для представления типа, так как оно адресует семейство типов.

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

// определение структуры Record на глобальном уровне: struct Record{ char Name [25] ; char Prione [15];}void main () { // создание объекта для хранения списка, содержащего не более // 50 структур типа Record: CList <Record, 50> RecordList; // создание и инициализация экземпляра структуры Record: Record Rec ={ "John", "287-981-0119" };// копирование содержимого объекта Rec в первый элемент списка:RecordList.Setltem (0, Rec) ;// продолжение функции main ... }

Совет

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