Бьерн Страуструп - Язык программирования С++. Главы 8-10 - Шаблоны типа и производные классы

ОГЛАВЛЕНИЕ


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 мы видели, что шаблоны типа дают удобное средство для создания целых семейств классов. Без шаблонов создание таких семейств только с помощью производных классов может быть утомительным занятием, а значит, ведущим к ошибкам. С другой стороны, если отказаться от производных классов и использовать только шаблоны, то появляется множество копий функций-членов шаблонных классов, множество копий описательной части шаблонных классов и во множестве повторяются функции, использующие шаблоны типа.