class="code"> int size() const { return sz; }
int capacity() const { return space; }
void resize(int newsize); // увеличение
void push_back(double d);
void reserve(int newalloc);
};
Обратите внимание на то, что этот класс содержит все основные операции (см. раздел 18.3): конструктор, конструктор по умолчанию, копирующий конструктор, деструктор. Он также содержит операции для доступа к данным (индексирование []), получения информации об этих данных (size() и capacity()), а также для управления ростом вектора (resize(), push_back() и reserve()).
19.3. Шаблоны
Однако нам мало иметь вектор, состоящий из чисел типа double; мы хотим свободно задавать тип элементов наших векторов. Рассмотрим пример.
vector<double>
vector<int>
vector<Month>
vector<Window*> // вектор указателей на объекты класса Window
vector< vector<Record> > // вектор векторов из объектов класса Record
vector<char>
Для этого мы должны научиться определять шаблоны. На самом деле мы с самого начала уже использовали шаблоны, но до сих пор нам не приходилось определять их самостоятельно. Стандартная библиотека содержит все необходимое, но мы не должны полагаться на готовые рецепты, поэтому следует разобраться, как спроектирована и реализована стандартная библиотека, например класс vector и функция sort() (разделы 21.1 и Б.5.4). Это не просто теоретический интерес, поскольку, как обычно, средства и методы, использованные при создании стандартной библиотеки, могут помочь при работе над собственными программами. Например, в главах 21-22 мы покажем, как с помощью шаблонов реализовать стандартные контейнеры и алгоритмы, а в главе 24 продемонстрируем, как разработать класс матриц для научных вычислений.
По существу, шаблон (template) — это механизм, позволяющий программисту использовать типы как параметры класса или функции. Получив эти аргументы, компилятор генерирует конкретный класс или функцию.
19.3.1. Типы как шаблонные параметры
Итак, мы хотим, чтобы тип элементов был параметром класса vector. Возьмем класс vector и заменим ключевое слово double буквой T, где T — параметр, который может принимать значения, такие как double, int, string, vector<Record> и Window*. В языке С++ для описания параметра T, задающего тип, используется префикс template<class T>, означающий “для всех типов T”.
Рассмотрим пример.
// почти реальный вектор элементов типа T
template<class T> class vector {
// читается как "для всех типов T" (почти так же, как
// в математике)
int sz; // размер
T* elem; // указатель на элементы
int space; // размер + свободная память
public:
vector():sz(0),elem(0),space(0) { }
explicit vector(int s);
vector(const vector&); // копирующий конструктор
vector& operator=(const vector&); // копирующее присваивание
~vector() { delete[] elem; } // деструктор
T& operator[](int n) { return elem[n]; } // доступ: возвращает
// ссылку
const T& operator[](int n) const { return elem[n]; }
int size() const { return sz; } // текущий размер
int capacity() const { return space; }
void resize(int newsize); // увеличивает вектор
void push_back(const T& d);
void reserve(int newalloc);
};
Это определение класса vector совпадает с определением класса vector, содержащего элементы типа double (см. раздел 19.2.6), за исключением того, что ключевое слово double теперь заменено шаблонным параметром T. Этот шаблонный класс vector можно использовать следующим образом:
vector<double> vd; // T — double
vector<int> vi; // T — int
vector<double*> vpd; // T — double*
vector< vector<int> > vvi; // T — vector<int>, в котором T — int
Можно просто считать, что компилятор генерирует класс конкретного типа (соответствующего шаблонному аргументу), подставляя его вместо шаблонного параметра. Например, когда компилятор видит в программе конструкцию vector<char>, он генерирует примерно такой код:
class vector_char {
int sz; // размер
char* elem; // указатель на элементы
int space; // размер + свободная память
public:
vector_char();
explicit vector_char(int s);
vector_char(const vector_char&); // копирующий конструктор
vector_char& operator=(const vector_char &); // копирующее
// присваивание
~vector_char (); // деструктор
char& operator[] (int n); // доступ: возвращает ссылку
const char& operator[] (int n) const;
int size() const; // текущий размер
int capacity() const;
void resize(int newsize); // увеличение
void push_back(const char& d);
void reserve(int newalloc);
};
Для класса vector<double> компилятор генерирует аналог класса vector, содержащий элементы типа double (см. раздел 19.2.6), используя соответствующее внутреннее имя, подходящее по смыслу конструкции vector<double>).
Иногда шаблонный класс называют порождающим типом (type generator). Процесс генерирования типов (классов) с помощью шаблонного класса по заданным шаблонным аргументам называется специализацией (specialization) или конкретизацией шаблона (template instantiation). Например, классы vector<char> и vector<Poly_line*> называются специализациями класса vector. В простых ситуациях, например при работе с классом vector, конкретизация не вызывает затруднений. В более общих и запутанных ситуациях конкретизация шаблона очень сильно усложняется. К счастью для пользователей шаблонов, вся эта сложность обрушивается только на разработчика компилятора.
Конкретизация шаблона (генерирование шаблонных специализаций) осуществляется на этапе компиляции или редактирования связей, а не во время выполнения программы.
Естественно, шаблонный класс может иметь функции-члены. Рассмотрим пример.
void fct(vector<string>& v)
{
int n = v.size();
v.push_back("Norah");
// ...
}
При вызове такой функции-члена шаблонного класса компилятор генерирует соответствующую конкретную функцию. Например, когда компилятор видит вызов
v.push_back("Norah"), он генерирует функцию
void vector<string>::push_back(const string& d) { /* ... */ }
используя шаблонное определение
template<class T> void vector<T>::push_back(const T& d) { /* ... */ };
Итак, вызову v.push_back("Norah") соответствует конкретная функция. Иначе говоря, если вам нужна функция с конкретным типом аргумента, компилятор сам напишет ее, основываясь на вашем шаблоне.
Вместо префикса template<class T> можно использовать префикс template <typename