Компания ALT Linux
Опубликован: 07.03.2015 | Доступ: свободный | Студентов: 2186 / 522 | Длительность: 24:14:00
Лекция 10:

Объектно-ориентированное программирование

10.6 Шаблоны классов

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

Классический пример ситуации, когда выгодно применять шаблоны классов — это так называемые контейнеры, т. е. классы, содержащие наборы некоторых значений (динамические списки, массивы, множества). Закладывая тип данных элемента как параметр шаблона, можно создать универсальный класс-контейнер, а на его основе порождать объекты для хранения наборов элементов конкретного типа — контейнер целых чисел, контейнер строк и т. д.

Описание шаблона класса также имеет много общего с шаблоном функции. Описание класса точно так же начинают с ключевого слова template, за которым следует список формальных параметров шаблона в угловых скобках. Этот же заголовок повторяется и перед описанием методов класса. В качестве параметров шаблона можно передавать типы данных или константы, но перед идентификатором, обозначающим тип данных, в списке формальных параметров ставится ключевое слово class.

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

Рассмотрим в качестве простого примера уже знакомый класс point, который хранит пару координат точки и на этот раз имеет метод info(), выводящий координаты на экран.

#include<iostream>
using namespace std;
template <class Type>
class point
{
	Type x, y;
	//.. .
	public :
	point ( Type x, Type y ) { this->x=x; this->y=y; }
	void info ( );
};
template <class Type>
void point<Type >::info ( )
{
	cout << "Координаты точки: x = " << x << ", y = " << y << endl;
}
main ( )
{
	point<float > f ( 10.1, 20.5 );
	f.info ( );
}

Как видим, конкретный тип (в нашем случае float) мы указали при создании объекта в угловых скобках. Точно так же мы могли указать любой стандартный тип данных, а могли — пользовательский тип, объявленный в программе. Однако необходимо помнить, что шаблоны функций и шаблоны классов могут работать только для тех типов данных (в т. ч. классов), которые поддерживают необходимые операции. Например, если мы захотим создать экземпляр класса point для хранения пары объектов какого-то собственного класса X, этот класс должен содержать конструктор копирования, а также поддерживать перегрузку двух использованных в point операторов:

class X
{
	..... .
public :
	X(X &); //конструктор копирования
	friend ostream& operator<<(X &);
	.....
};

Как упоминалось, в качестве параметров шаблона можно передавать и константы. Рассмотрим пример, где константа передаётся шаблонному классу square_matrix, хранящему квадратную матрицу заданной размерности. Такой шаблон позволит легко создавать объекты типов "матрица 20х20 целых чисел", или "матрица 10х10 типа double".

#include <iostream>
using namespace std;

template <class Type, int n>
class square_matrix
{
	Type * data;
public :
	square_matrix ( ) { data=new Type [ n*n ]; }
	void print ( );
//.. .
};
template <class Type, int n>
void square_matrix<Type, n >::print ( )
{
	for ( int i =0; i<n; i++)
	{
		for ( int j =0; j<n; j++)
		{
			cout << data[ i *n+j ] << " \t ";
		}
	cout << endl;
	}
}
int main ( )
{
	cout << "Матрица 5х5 целых чисел: \n ";
	square_matrix<int, 5> m1;
	m1.print ( );
	cout << "Матрица 10х10 значений типа double: \n ";
	square_matrix<double, 10> m2;
	m2.print ( );
	return 0;
}

Для простоты приведённый пример умеет только порождать матрицу заданного размера с нулевыми элементами, а также построчно выводить её на экран.

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

10.6.1 Типаж шаблона

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

template<class Type>
Type min ( Type a,Type b )
{
	return a<b? a:b;
}
char *min ( char *a, char *b )
{
	strcmp ( a,b )<0?a:b;
}

С шаблонами классов может возникать аналогичная проблема, но решается она обычно несколько иначе.

Предположим, при создании шаблонного класса matrix, работающего с матрицами элементов произвольного типа, оказалось, что нет возможности создать код, одинаково обрабатывающий все нужные типы данных — т. е. работа некоторых методов класса должна зависеть от типа элементов матрицы. В довершение ко всему, типы элементов матрицы могут быть встроенными типами данных C++, поэтому нет никакой возможности заставить тип элемента нести дополнительную информацию об особенностях его обработки. В результате написать единственный шаблонный класс оказывается невозможно, а его переписывание под каждый конкретный тип данных возможно, но по определению лишено смысла:

template <class Type>
class matrix
{
	//.. .
};
template<>
class matrix<long>
{
	//.. .
};
template<>
class matrix<int>
{
	//.. .
};
...

Фактически это означает несколько раз переписать класс заново.

Ситуация ещё более осложняется, если разработчик класса хочет оставить возможность для использования в нём новых типов элементов (без переписывания класса с нуля).

В такой ситуации всё, что зависит от изменений типа данных, собирают в одном месте и называют типажом (англ. "trait"). Типаж передают классу как ещё один параметр шаблона. Типаж является небольшим по объёму классом, содержащим все операции, зависящие от типа данных, и при необходимости его несложно переписать под нужный тип. Более того, поскольку для большинства типов никакого специфического поведения не требуется, в типаж сам по себе может быть шаблоном класса. В этом случае как дополнительный параметр шаблона будет передаваться шаблонный класс, основанный на том же типе, что передан в качестве основного параметра — кроме тех редких случаев, когда требуется реализовать специфическое поведение:

template<class Type>
class MatrixTraits
{
	//.. .
};
template<>
class MatrixTraits <int>
{
	//.. .
};
template<class Type, class Traits >
class matrix
{
	//.. .
};
...
matrix <int, MatrixTraits <int>> m1.

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

10.6.2 Пример реальной иерархии шаблонов

Стандартная библиотека C++ практически полностью построена на шаблонах и потому представляет достаточно примеров профессионального использования данного механизма. Рассмотрим в качестве наглядной иерархии шаблонов уже знакомые нам классы потокового ввода-вывода.

Изначально библиотека потокового ввода-вывода действительно представляла собой такую иерархию классов, которая изображена на рис. 10.4. Однако по мере увеличения спроса на приложения, работающие с текстом сразу на нескольких языках, встал вопрос о поддержке кодировки Unicode, позволяющей совмещать в одной строке символы разных национальных алфавитов. В зависимости от языка, символ в Unicode может кодироваться различным количеством байт — от одного до четырёх. В C++ для поддержки таких символов существует тип wchar_t (от англ. wide characters — "широкие символы"). Фактически понадобилось создать иерархию классов, аналогичную классам iostream, но работающих с типом данных wchar_t вместо char. В итоге библиотека iostream была переработана на основе механизма шаблонов.

Классы, основанные на шаблонах, носят имена, аналогичные описанным в разделе 10.4.6, с добавлением приставки "basic" и знака подчёркивания: basic_ios, basic_istream, basic_ostream и т. д. Привычные программисту имена классов для работы с символами типа char (как, впрочем, и с wchar_t) реализованы через подстановку имени типа в конструкции typedef:

typedef basic_ios <char> ios;
typedef basic_ios <wchar_t> w ios;
typedef basic_istream<char> istream;
typedef basic_istream<wchar_t> w istream;
..... .

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

Хотя по виду конструкции typedef может показаться, что шаблоны библиотеки потокового ввода-вывода имеют один параметр, на самом деле это не совсем так. Второй параметр — это как раз типаж символов, т. е. отдельный шаблонный класс, который реализует базовые операции с символами и строками для заданного типа символов. Эти базовые операции — присваивание символов, копирование и сравнение их последовательностей, приведение к целому типу и др. Данный параметр имеет значение по умолчанию, и потому может не использоваться при объявлении экземпляров шаблона. В оригинале же шаблоны классов потокового ввода-вывода выглядят следующим образом (в виду однотипности, приведём по одному примеру для стандартного ввода-вывода, работы с файлами и со строками):

template <class charT, class traits=char_traits <charT> > basic_istream;
template <class charT, class traits=char_traits <charT> > basic_ifstream;
template <class charT, class Traits = char_ Traits <charT >, class allocator =
allocator <charT> > basic_istringstream;

Именно эти потоковые шаблоны определяют на самом деле методы для разбора и форматирования, являющиеся перегруженными версиями операторов ввода operator>> и вывода operator<<.

Аналогично реализованы шаблоны для потоковых буферов:

template <class charT, class traits = char_traits <charT> > basic_streambuf;
template <class charT, class traits = char_traits <charT> > basic _filebuf;
Сергей Радыгин
Сергей Радыгин

Символы кириллицы выводит некорректно. Как сделать чтобы выводился читабельный текст на русском языке?

Тип приложения - не Qt,

Qt Creator 4.5.0 основан на Qt 5.10.0. Win7.

 

Юрий Герко
Юрий Герко

Кому удалось собрать пример из раздела 13.2 Компоновка (Layouts)? Если создавать проект по изложенному алгоритму, автоматически не создается  файл mainwindow.cpp. Если создавать этот файл вручную и добавлять в проект, сборка не получается - компилятор сообщает об отсутствии класса MainWindow. Как правильно выполнить пример?