Шаблоны типа
8.7 Шаблоны типа и производные классы
Мы уже видели, что сочетание производных классов (наследование) и шаблонов типа может быть мощным средством. Шаблон типа выражает общность между всеми типами, которые используются как его параметры, а базовый класс выражает общность между всеми представлениями (объектами) и называется интерфейсом. Здесь возможны некоторые простые недоразумения, которых надо избегать.
Два созданных по одному шаблону типа будут различны и между ними невозможно отношение наследования кроме единственного случая, когда у этих типов идентичны параметры шаблона. Например:
template<class T>
class Vector { /* ... */ }
Vector<int> v1;
Vector<short> v2;
Vector<int> v3;Здесь v1 и v3 одного типа, а v2 имеет совершенно другой тип. Из того факта, что short неявно преобразуется в int, не следует, что есть неявное преобразование Vector<short> в Vector<int>:
v2 = v3; // несоответствие типов
Но этого и следовало ожидать, поскольку нет встроенного преобразования int[] в short[].
Аналогичный пример:
class circle: public shape { /* ... */ };
Vector<circle*> v4;
Vector<shape*> v5;
Vector<circle*> v6;Здесь v4 и v6 одного типа, а v5 имеет совершенно другой тип. Из того факта, что существует неявное преобразование circle в shape и circle* в shape*, не следует, что есть неявные преобразования Vector<circle*> в Vector<shape*> или Vector<circle*>* в Vector<shape*>*:
v5 = v6; // несоответствие типов
Дело в том, что в общем случае структура (представление) класса, созданного по шаблону типа, такова, что для нее не предполагаются отношения наследования. Так, созданный по шаблону класс может содержать объект типа, заданного в шаблоне как параметр, а не просто указатель на него. Кроме того, допущение подобных преобразований приводит к нарушению контроля типов:
void f(Vector<circle>* pc)
{
Vector<shape>* ps = pc; // ошибка: несоответствие типов
(*ps)[2] = new square; // круглую ножку суем в квадратное
// отверстие (память выделена для
// square, а используется для circle
}На примерах шаблонов Islist, Tlink, Slist, Splist, Islist_iter, Slist_iter и SortableVector мы видели, что шаблоны типа дают удобное средство для создания целых семейств классов. Без шаблонов создание таких семейств только с помощью производных классов может быть утомительным занятием, а значит, ведущим к ошибкам. С другой стороны, если отказаться от производных классов и использовать только шаблоны, то появляется множество копий функций-членов шаблонных классов, множество копий описательной части шаблонных классов и во множестве повторяются функции, использующие шаблоны типа.
8.7.1 Задание реализации с помощью параметров шаблона
В контейнерных классах часто приходится выделять память. Иногда
бывает необходимо (или просто удобно) дать пользователю возможность
выбирать из нескольких вариантов выделения памяти, а также позволить
ему задавать свой вариант. Это можно сделать несколькими способами.
Один из способов состоит в том, что определяется шаблон типа для
создания нового класса, в интерфейс которого входит описание
соответствующего контейнера и класса, производящего выделение памяти
по способу, описанному в
6.7.2:
template<class T, class A> class Controlled_container
: public Container<T>, private A {
// ...
void some_function()
{
// ...
T* p = new(A::operator new(sizeof(T))) T;
// ...
}
// ...
};Шаблон типа здесь необходим, поскольку мы создаем контейнерный класс. Наследование от Container<T> нужно, чтобы класс Controlled_container можно было использовать как контейнерный класс. Шаблон типа с параметром A позволит нам использовать различные функции размещения:
class Shared : public Arena { /* ... */ };
class Fast_allocator { /* ... */ };
Controlled_container<Process_descriptor,Shared> ptbl;
Controlled_container<Node,Fast_allocator> tree;
Controlled_container<Personell_record,Persistent> payroll;Это универсальный способ предоставлять производным классам содержательную информацию о реализации. Его положительными качествами являются систематичность и возможность использовать функции-подстановки. Для этого способа характерны необычно длинные имена. Впрочем, как обычно, typedef позволяет задать синонимы для слишком длинных имен типов:
typedef Controlled_container<Personell_record,Persistent> pp_record; pp_record payroll;
Обычно шаблон типа для создания такого класса как pp_record используют
только в том случае, когда добавляемая информация по реализации
достаточно существенна, чтобы не вносить ее в производный класс ручным
программированием. Примером такого шаблона может быть общий
(возможно, для некоторых библиотек стандартный) шаблонный класс
Comparator (
8.4.2), а также нетривиальные (возможно, стандартные
для некоторых библиотек) классы Allocator (классы для выделения памяти).
Отметим, что построение производных классов в таких примерах
идет по "основному проспекту", который определяет интерфейс с
пользователем (в нашем примере это Container ). Но есть и "боковые
улицы", задающие детали реализации.
