Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 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). Но есть и "боковые       улицы", задающие детали реализации.