Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
С++ Страуструп.doc
Скачиваний:
4
Добавлен:
18.04.2019
Размер:
2.72 Mб
Скачать

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). Но есть и "боковые

улицы", задающие детали реализации.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]