Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Итог_Пособие C++.doc
Скачиваний:
8
Добавлен:
01.07.2025
Размер:
2 Мб
Скачать
☆

5.2 Шаблонные классы

Шаблонные классы создаются аналогично шаблонным функциям. Для примера создадим шаблонный класс PasArray, позволяющий работать с массивами в стиле языка Pascal – в таких массивах левая граница не обязана быть равна нулю (и даже может быть отрицательной). По сути, наш шаблонный класс будет представлять собой обёртку над шаблонным классом std::vector. В операторе индексирования (operator[]) вычисляется настоящая позиция элемента в векторе и возвращается ссылка на этот элемент.

// Пример 5.3 - шаблонный класс PasArray

template<class T>

class PasArray{

int left; // левая граница

std::vector<T> data; // вектор для хранения элементов

public:

PasArray(int left, int right) :

left(left),

data (right - left + 1) {}

T& operator [] (int index) {

return data[index - left];

}

};

int main() {

PasArray<int> a(-5, 5);

for (int i = -5; i <= 5; i++)

a[i] = i;

}

Поскольку при создании объекта шаблонного класса, в отличие от функции, компилятор не может автоматически определить тип параметра, то нам пришлось указывать его явным образом в угловых скобках.

Если углубиться немного в теорию языков программирования, то можно сказать, что шаблоны языка C++ являются реализацией методологии обобщённого программирования (generic programming). Суть обобщённого программирования заключается в таком описании данных и алгоритмов, которое можно применять к различным типам данных, не меняя само это описание. Как и объектно-ориентированное программирование (ООП), обобщенное программирование (ОП) в C++ основывается на полиморфизме, только ООП основано на полиморфизме подтипов, а ОП – на параметрическом (статическом) полиморфизме.

5.3 Специализация шаблонов

Иногда бывает нужно сделать так, чтобы для определённого типа данных шаблон функции или класса инстанцировался особым образом – не так, как для остальных типов. Рассмотрим пример. Пусть нам требуется написать шаблонную функцию swap, меняющую два своих аргумента местами. Вначале реализуем обычный способ замены двух переменных – с помощью вспомогательной третьей:

// Пример 5.4 - шаблонная функция swap

template<class T>

void swap(T &x, T&y) {

T tmp;

tmp = x; x = y; y = tmp;

}

Однако, для переменных целого типа существует интересный вариант замены, не требующий вспомогательной переменной. В нём используется битовая операция ^ (исключающее или). Допустим, нам по какой-то причине хочется, чтобы переменные типа int функция swap меняла местами именно таким способом. Для этого нужно выполнить специализацию этой функции для типа int:

// Пример 5.5 - специализация шаблона swap для типа int

template<>

void swap<int> (int &x, int &y) {

if (&x != &y) {

x ^= y; y ^= x; x ^= y;

}

}

Обратите внимание на синтаксис: после слова template пишутся пустые угловые скобки, а после названия функции добавляется <int>. Примечание: для практических целей такой способ замены переменных вряд ли имеет какие-либо плюсы по сравнению с обычным.

Похожим образом выполняется и специализация шаблонных классов. Типичным примером из стандартной библиотеки является класс std::vector – он по-особому работает с типом данным bool. Хотя переменная типа bool обычно занимает один байт, но в std::vector<bool> каждый элемент занимает всего лишь один бит (это позволяет получить восьмикратную экономию памяти по сравнению с булевским массивом).

Интересно, что из-за этой особенности класса std::vector описанный в предыдущем пункте шаблонный класс PasArray не будет нормально работать с типом bool. Например, следующий код вызовет ошибку компиляции:

PasArray<bool> a(1, 5);

a[1] = true; // ошибка компиляции при попытке получить

// неконстантную ссылку на элемент вектора

Чтобы это исправить, нам нужно по-особому реализовать класс PasArray для типа bool, то есть выполнить специализацию этого шаблонного класса. Сделать это можно разными способами. Простейший вариант – вместо вектора из bool использовать вектор из char:

// Пример 5.6 - специализация шаблона PasArray для типа bool

template<>

class PasArray<bool>{

int left;

std::vector<char> data;

public:

PasArray(int left, int right) :

left(left),

data (right - left + 1) {}

char& operator [] (int index) {

return data[index - left];

}

};

Заметим, что для решения конкретно этой проблемы можно обойтись и без специализации – достаточно разобраться по документации, что именно возвращает функция operator[] в классе std::vector. Данная операторная функция возвращает std::vector::reference, где reference – это ссылка на заданный элемент вектора (кроме std::vector<bool>), и это объект специального вспомогательного класса с перегруженной операцией присваивания для случая std::vector<bool>. Таким образом, нам достаточно лишь написать, что в классе PasArray функция operator[] будет возвращать тип std::vector<T>::reference. При этом отпадает необходимость в специализации, к тому же сохраняется экономия памяти, которую даёт вектор для типа bool:

// Пример 5.7 - реализация шаблонного класса PasArray,

// не требующая специализации для типа bool

template<class T>

class PasArray{

int left;

std::vector<T> data;

public:

PasArray(int left, int right) :

left(left),

data (right - left + 1) {}

typename std::vector<T>::reference operator [] (int index)

{

return data[index - left];

}

};

Обратите внимание на ключевое слово typename – оно подсказывает компилятору, что запись std::vector<T>::reference означает тип (а не переменную или константу). Компилятору затруднительно определить это автоматически (так как в этом выражении используется фиктивный тип T), поэтому стандарт требует использовать слово typename в подобных случаях.