Литмир - Электронная Библиотека
A
A

При компиляции программы, использующей шаблоны, компилятор “заглядывает” внутрь шаблонов и его шаблонных аргументов. Он делает это для того, чтобы извлечь информацию, необходимую для генерирования оптимального кода. Для того чтобы эта информация стала доступной, современные компиляторы требуют, чтобы шаблон был полностью определен везде, где он используется. Это относится и к его функциям-членам и ко всем шаблонным функциям, вызываемым из них. В результате авторы шаблонов стараются разместить определения шаблонов в заголовочных файлах. На самом деле стандарт этого не требует, но пока не будут разработаны более эффективные реализации языка, мы рекомендуем вам поступать со своими шаблонами именно так: размещайте в заголовочном файле определения всех шаблонов, используемых в нескольких единицах трансляции.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Мы рекомендуем вам начинать с очень простых шаблонов и постепенно набираться опыта. Один из полезных приемов проектирования мы уже продемонстрировали на примере класса
vector
: сначала разработайте и протестируйте класс, используя конкретные типы. Если программа работает, замените конкретные типы шаблонными параметрами. Для обеспечения общности, типовой безопасности и высокой производительности программ используйте библиотеки шаблонов, например стандартную библиотеку языка C++. Главы 20-21 посвящены контейнерам и алгоритмам из стандартной библиотеки. В них приведено много примеров использования шаблонов.

19.3.3. Контейнеры и наследование

Это одна из разновидностей сочетания объектно-ориентированного и обобщенного программирования, которое люди постоянно, но безуспешно пытаются применять: использование контейнера объектов производного класса в качестве контейнера объектов базового класса. Рассмотрим пример.

vector<Shape> vs;

vector<Circle> vc;

vs = vc;    // ошибка: требуется класс vector<Shape>

void f(vector<Shape>&);

f(vc);      // ошибка: требуется класс vector<Shape>

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 Но почему? “В конце концов, — говорите вы, — я могу конвертировать класс
Circle
в класс
Shape
!” Нет, не можете. Вы можете преобразовать указатель
Circle*
в
Shape*
и ссылку
Circle&
в
Shape&
, но мы сознательно запретили присваивать объекты класса
Shape
, поэтому вы не имеете права спрашивать, что произойдет, если вы поместите объект класса Circle с определенным радиусом в переменную типа
Shape
, которая не имеет радиуса (см. раздел 14.2.4). Если бы это произошло, — т.е. если бы мы разрешили такое присваивание, — то возникло бы так называемое “усечение” (“slicing”), похожее на усечение целых чисел (см. раздел 3.9.2).

Итак, попытаемся снова использовать указатели.

vector<Shape*> vps;

vector<Circle*> vpc;

vps = vpc;  // ошибка: требуется класс vector<Shape*>

void f(vector<Shape*>&);

f(vpc);     // ошибка: требуется класс vector<Shape*>

И вновь система типов сопротивляется. Почему? Рассмотрим, что может делать функция

f()
.

void f(vector<Shape*>& v)

{

  v.push_back(new Rectangle(Point(0,0),Point(100,100)));

}

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 Очевидно, что мы можем записать указатель
Rectangle*
в объект класса
vector<Shape*>
. Однако, если бы этот объект класса
vector<Shape*>
в каком-то месте программы рассматривался как объект класса
vector<Circle*>
, то мог бы возникнуть неприятный сюрприз. В частности, если бы компилятор пропустил пример, приведенный выше, то что указатель
Rectangle*
делал в векторе
vpc
? Наследование — мощный и тонкий механизм, а шаблоны не расширяют его возможности неявно. Существуют способы использования шаблонов для выражения наследования, но эта тема выходит за рамки рассмотрения этой книги. Просто запомните, что выражение “
D
— это
B
” не означает: “
C<D>
— это
C<B>
” для произвольного шаблонного класса
C
. Мы должны ценить это обстоятельство как защиту против непреднамеренного нарушения типов. (Обратитесь также к разделу 25.4.4.) 

19.3.4. Целые типы как шаблонные параметры

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Очевидно, что параметризация классов с помощью типов является полезной. А что можно сказать о параметризации классов с помощью, например, целых чисел или строк? По существу, любой вид аргументов может оказаться полезным, но мы будем рассматривать только типы и целочисленные параметры. Другие виды параметров реже оказываются полезными, и поддержка языком С++ других видов параметров носит более сложный характер и требует обширных и глубоких знаний.

Рассмотрим пример наиболее распространенного использования целочисленного значения в качестве шаблонного аргумента: контейнер, количество элементов которого известно уже на этапе компиляции.

template<class T, int N> struct array {

  T elem[N]; // хранит элементы в массиве -

  // члене класса, использует конструкторы по умолчанию,

  // деструктор и присваивание

  T& operator[] (int n); // доступ: возвращает ссылку

  const T& operator[] (int n) const;

  T* data() { return elem; } // преобразование в тип T*

  const T* data() const { return elem; }

  int size() const { return N; }

}

Мы можем использовать класс

array
(см. также раздел 20.7) примерно так:

259
{"b":"847443","o":1}