- •Цель работы
- •2 Техническое обеспечение
- •3 Программное обеспечение
- •4 Постановка задачи
- •Общие сведения
- •5.1 Шаблоны классов
- •5.1.1 Пример класса-шаблона
- •5.1.2 Параметры шаблона, задаваемые по умолчанию
- •5.1.3 Специализация шаблона класса
- •5.1.4 Статические элементы в шаблонах
- •5.1.5 Шаблоны классов с шаблонами
- •5. 2 Шаблоны функций
- •5.3 Обобщенные алгоритмы и функторы
- •Варианты заданий
- •7 Указания к выполнению лабораторной работы
- •9 Контрольные вопросы
5.1.4 Статические элементы в шаблонах
В классе-шаблоне разрешено объявлять и статические методы, и статические поля. В этом случае при различных вариантах параметров шаблона получаются фактически разные конкретные классы, и каждый из них обладает собственной копией статических членов, как показано в листинге 11.2.
Листинг 11.2. Класс-шаблон со статическими элементами
template <typename Т>
class StaticClass
{
static T t; // зависимое имя
static int count; // независимое имя
public:
StaticClass() { ++count; } // конструктор считает объекты
~StaticClass ()_ { --count; } // деструктор
static void print(T x = t); // статический метод
template <typename T> int StaticClass <T>::count = 0; // счетчик объектов
template <typename T> T StaticClass<T>:: t = T(); // определение t
template <class T> // реализация метода
void StaticClass<T> :: print(T x)
{ std::cout << x << “ “ << count; t = x; }
В классе определен счетчик объектов статическое поле count. Это поле не зависит от параметра шаблона, однако при определении поля нужно соблюдать синтаксис для элементов класса-шаблона. Определение статического поля t, тип которого зависит от шаблона, можно задать без явного вызова безаргументного конструктора, так как он вызывается по умолчанию:
template <typename Т>
Т StaticClass <T>::t; // вызов конструктора по умочанию
Реализация статического метода, как обычно, должна сопровождаться префиксом шаблона template <class Т>.
Разрешается специализировать статические поля:
template <> int StaticClass <int>::t = 22; // специализация t
template <> double StaticClass<double>::t = 15; // специализация t
Специализация статического поля вызывает инстанцирование шаблона с параметром заданного типа. В данном случае создаются классы StaticClass <int> и StaticC1ass <double>. Для каждого из них заводятся статические поля-счетчики count, статические поля t, которым присваиваются значения, и создаются статические методы print ().
5.1.5 Шаблоны классов с шаблонами
К шаблонам применимо, как в экономике, отношение «шаблон-класс-шаблон». Нет ограничений на вложенность классов-шаблонов: класс может быть объявлен внутри шаблона и шаблон внутри как класса, так и шаблона. Единственное ограничение класс-шаблон нельзя объявлять внутри функции (а обычный класс можно). Шаблон может наследовать от обычного класса; класс может наследовать от шаблона; шаблон может наследовать от шаблона. В классе-шаблоне допускаются вложенные конструкции:
инстанцированный класс-шаблон в качестве поля в классе и в шаблоне;
класс-шаблон в качестве параметра-типа по умолчанию;
метод-шаблон, в том числе конструктор.
Поле-шаблон
В классе допускается объявить поле, инстанцировав некоторый шаблон. Объявление поля-шаблона ничем не отличается от объявления обычного поля. Например, на основе класса-шаблона Array (см. листинг 11.1) можно реализовать стек ограниченного размера.
class Stack
{
Array<double. 100> t; // поле - инстанцированный шаблон
public:
// методы
};
Объявить объект-стек можно обычным образом:
Stack t;
Поле-шаблон в шаблоне выглядит так:
template <typename Т, std::size_t N = 100> // параметры стека class Stack
{
Аггау<Т, N> st; // зависимое имя
public:
// методы
};
Так как количество элементов стека явно не объявлено, определять переменные Stack разрешено так:
Stack<double> st;
Или с явным указанием количества элементов
Stack <double, 500> stack;
Параметр-шаблон
Разрешается использовать в качестве параметра шаблон. Параметру-шаблону можно присвоить значение по умолчанию, например Array (листинг 11.1).
template <typename Т,
// тип элементов
std::size_t N = 100, // количество
template <class, std::size_t>
// параметр-шаблон
class Container = Array
> // конец списка
class Stack
{
Container <T, N> s; // поле-контейнер
public:
// методы
};
Параметр-шаблон объявлен последним:
template <class, std::size_t> class Container = Array>
Объявление переменных-стеков в программе может выглядеть так:
Stack <double> s1; // размер и контейнер по умолчанию
Stack <int, 500> s2; // контейнер по умолчанию
Stack <long, 1000, Array> s3; // все задано явно
В третьем случае задается только имя контейнера без аргументов. Вместо шаблона Array разрешается указывать любой контейнер, имеющий два параметра: тип элементов и беззнаковое целое.
Метод-шаблон
В любом классе, как в обычном, так и в шаблоне, можно объявить метод-шаблон. Класс-шаблон Array (листинг 11.1) не позволяет присваивать массивы разной длины и (или) массивы с разными типами элементов. Этот недостаток можно устранить, если определить в классе Array шаблон операции присваивания, как показано в листинге 11.3.
Листинг 11.3. Метод-шаблон в классе-шаблоне
#include <cstdlib> // для size_t
#include <stdexcept>
template <typename T = double, std::size_t N = 10>
// параметры шаблона
class Array
{
public:
// типы и методы (см. листинг 11.1)
template <typename Tl, std::size_t NI>
Array <T, N> &operator=(const Array <TI, NI> &r);
private:
// . . .
T elem[N]; // поле-массив
};
// реализация конструкторов и методов
// Определение метода-шаблона
template <class Т, std::size_t N>
// внешний шаблон
template <class Tl, std::size_t NI> // внутренний шаблон
Array<T, N> &Array <T, N>::operator=(const Array <TI, NI> &rhs)
{
if ((void *) this != (void *) &rhs) // проверка самоприсваивания
{
if (N >= NI) // проверка размера
for(int i = 0; i<NI; ++i)
elem[i] = static_cast <T>(rhs[i]); // преобразование типа
}
return *this;
}
Прототип метода выглядит следующим образом:
template <typename Tl, std::size_t NI>
Array <T,N> &operator=(const Array <TI, NI> &rhs);
Определение метода-шаблона начинается с заголовка template шаблона-класса, а затем задается его собственный заголовок template. Методы-шаблоны не могут быть виртуальными; для обычных методов класса-шаблона такого ограничения нет.
Шаблон операции присваивания не замещает оператор присваивания, генерируемый по умолчанию. Для присвоения массивов одного типа будет вызываться стандартный оператор присваивания. Аналогично, шаблон конструктора копирования никогда не замещает собой генерируемый конструктор.
С помощью шаблона-присваивания выполняется присвоение «меньшего» массива «большему» – как по размеру, так и по типу.
Array <int, 10> mn;
Array <int, 10> mm; // тип и размер mm = m
mm = mn; // встроенная операция
Array <int, 5> х; // размер х < размер mn
mn = х; // операция-шаблон
Array<double, 15> у;
// тип и размер отличаются
у = х; // операция-шаблон
Шаблоны и наследование
Шаблон может наследовать от простого класса такой базовый класс называется независимым базовым классом.
class Base { };
template <class T>
class Template: public Base
{
// . . .
};
Это может понадобиться, например, для того, чтобы сделать статические поля общими для любых классов, инстанцированных из шаблона-наследника.
class CommonVariable
{
public:
static const double Exp;
static const double Pi;
};
const double CommonVariable::Exp = 2.718281828;
const double CommonVariable::Pi = 3.141592653;
template <typename T>
class DeriveTemplate: public CommonVariable
{
// . . .
};
Здесь статические поля Exp и Pi будут общими для всех классов, инстанцированных из шаблона DeriveTemplate.
Шаблон может наследовать от шаблона.
template <typename T1, typename T2> // базовый класс-шаблон
struct Pair { T1 first; T2 second; };
template <class T> // шаблон-наследник
class Derive: public Pair <T,T>
{ };
Простой класс также может наследовать от шаблона. Простой класс не предполагает параметров шаблона, наследование выполняется фактически не от шаблона, а от конкретного класса, инстанцированного из шаблона.
template <class Т> // базовый класс-шаблон
class Base { };
class Derave: public Base<date> // наследник - простой класс
{ };
Последнее используется для отслеживания количества объектов класса, как это сделано в листинге 11.4.
Листинг 11.4. Подсчет объектов класса
template <class Т> // шаблон класса со счетчиком
class Count
{
static unsigned int counter; // счетчик
public:
// функциональность подсчета объектов
Count() { ++counter; }
Count(const Count <T> &t) { ++counter; }
~Count() { --counter; }
static unsigned int getCounter() { return counter; }
};
template <class T> // инициализация счетчика
unsigned int CountedObject <T>::counter = 0;
// наследование от шаблона
class Object01: public Count <Object01>
{
// дополнительная функциональность
}:
class 0bject02: public Count <Object02>
{
// дополнительная функциональность
};
В таком варианте каждый из классов-наследников будет иметь собственную копию счетчика объектов.
Шаблоны и дружественные отношения
У шаблона могут быть друзья, и шаблоны тоже могут дружить. Дружественная функция вывода для класса-шаблона должна быть такой:
template <typename Т>
class TempiateClass
{
T x;
public:
TemplateClass(const T &t) : x(t) { } // конструктор инициализации
friend
std: :ostream &operator<< <>(std::ostream &os, const TempiateClass <T> &t);
};
// внешнее определение
template <class T>
std: :ostream &operator<<(std: :ostream &os. const TempiateClass<T>&t)
{ return os << '(' << t.x << ')'; }
Пустые скобки <>, указанные в прототипе после имени функции, свидетельствуют, что дружественная функция является шаблоном. Для дружественных функций-шаблонов не выполняются по умолчанию преобразования типов аргументов. При определении дружественной функции внутри класса-шаблона ее аргументы обязаны зависеть от параметров шаблона. Определенные внутри класса-шаблона функции-друзья не являются функциями-шаблонами это обычные функции, несмотря на то что аргументы у них зависят от параметра шаблона.
template <class Т>
class Template
{
Т х;
public:
Template(const Т &t) : x(t) { }
friend std::ostream &operator<<(std::ostream &os, const Template <T> &t)
{ return os << '(' << t.x << ')'; }
};
