Опубликован: 10.10.2006 | Доступ: свободный | Студентов: 6086 / 462 | Оценка: 4.26 / 3.88 | Длительность: 31:30:00
Лекция 8:

Шаблоны типа

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 Задание реализации с помощью параметров шаблона

В контейнерных классах часто приходится выделять память. Иногда бывает необходимо (или просто удобно) дать пользователю возможность выбирать из нескольких вариантов выделения памяти, а также позволить ему задавать свой вариант. Это можно сделать несколькими способами. Один из способов состоит в том, что определяется шаблон типа для создания нового класса, в интерфейс которого входит описание соответствующего контейнера и класса, производящего выделение памяти по способу, описанному в \S 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 ( \S 8.4.2), а также нетривиальные (возможно, стандартные для некоторых библиотек) классы Allocator (классы для выделения памяти). Отметим, что построение производных классов в таких примерах идет по "основному проспекту", который определяет интерфейс с пользователем (в нашем примере это Container ). Но есть и "боковые улицы", задающие детали реализации.

Дарья Федотова
Дарья Федотова
Сергей Березовский
Сергей Березовский

В рамках проф. переподготовки по программе "Программирование"

Есть курсы, которые я уже прошел. Но войдя в курс я вижу, что они не зачтены (Язык Ассемблера и архитектура ЭВМ, Программирование на С++ для профессионалов). Это как?

Роман Островский
Роман Островский
Украина
Оксана Пагина
Оксана Пагина
Россия, Москва