Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.

Предоплата всего

Подписываем
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Предоплата всего
Подписываем
Рубанчик В. Б. |
Лабораторная №4 Шаблоны функций. Шаблоны классов (параметризованные типы). |
6/6 |
Объявления и описания функций-шаблонов начинаются с ключевого слова template (англ. шаблон), за которым в угловых скобках записывается список формальных параметров шаблона (формальных параметров не функции, а шаблона!), которые называют параметрами настройки шаблона. Этот список должен содержать по крайней мере один элемент (т.е. не может быть пустым).
Параметры настройки шаблона при генерации (с помощью шаблона) конкретной функции принимают "значения" описателей базовых или пользовательских типов данных.
Описание каждого параметра настройки шаблона использует ключевое слово class, которое говорит о том, что параметризуется тип. Заметим, что слово class применяется для спецификации любых типов данных, в том числе и базовых типов языка, а не только типов-классов.
Параметры настройки шаблона в списке параметров должны различаться по именам.
Пример.
Функция swap обменивает значениями две переменные:
template <class T> или void swap(T &x, T &y) { T z; z=x; x=y; y=z;} |
template <class T> void swap(T *x, T *y) { T z; z=*x; *x=*y; *y=z;} |
Шаблон функции - это объявление семейства функций. Сам по себе он не определяет ничего конкретного, но может служить скелетом для порождения бесконечного множества экземпляров функций. Встретив шаблон, компилятор только запоминает внутреннюю структуру функции. Затем, обрабатывая вызов функции, требующий использования шаблонной функции, компилятор генерирует конкретный вариант функции.
Параметр настройки шаблона в теле шаблона используется как спецификатор типа. С его помощью можно описывать переменные, выполнять операции приведения типа или описывать возвращаемое значение функции.
Каждый включенный в список параметр настройки шаблона должен появиться в списке аргументов функции хотя бы один раз. Это требование связано с механизмом конкретизации этих "типовых" переменных. Использование параметра только для описания типа возвращаемого значения недостаточно.
Для шаблонной функции может быть объявлен прототип, причем имена параметров настройки шаблона в прототипе и в описании функции могут различаться (как и в случае обычных аргументов функции). Однако объявление шаблона (прототип) может быть только глобальным, то есть, размещено на внешнем уровне.
//прототип
template <class T> T f(T x);
//описание функции
template <class U> U f(U x)
{...}
Параметры настройки шаблона как обычные локальные переменные затеняют в описании шаблонной функции одноименные глобальные переменные. Доступ к таким глобальным переменным осуществляется по обычным правилам для скрытых переменных:
int T;
template <class T> T f(T x) {
T a; // "локальное" T
::T=1; // "глобальное" T }
Функция-шаблон может быть описана как статическая и подставляемая. Спецификаторы inline и static записывают за списком формальных параметров шаблона, но не перед template. Функции-шаблоны могут быть перегружены другими шаблонными или нешаблонными функциями.
Процесс построения конкретной функции на основании шаблона называют конкретизацией. Конкретизация функции-шаблона в программе происходит в двух случаях:
Например, если определен шаблон
template <class T> T f(T x,T y){ return x+y;}
то конкретизация будет производиться в каждом из следующих трех случаев использования функции f:
int i=2, j=3, k=f(i,j);//генерируется функция с аргументами int
long (*pfl)(long,long)=f; //генерируется функция с аргументами long
float (*pff)(float,float)=f; //генерируется функция с аргументами float
Каким образом производится конкретизация шаблона функции? Очевидно, нужно каким-то образом установить, какой фактический тип соответствует в данном вызове функции каждому параметризованному типу шаблона.
Этот соответствие устанавливается на основе сопоставления формальных параметров шаблона, используемых в описании аргументов функции с типами фактических параметров вызова функции.
Для этого в шаблоне ищутся аргументы с параметризованными типами, определяются типы соответствующих им фактических параметров и производится замена параметризованных типов на фактические.
Если формальный тип в шаблоне встречается в списке аргументов шаблона функции несколько раз, то каждая следующая привязка должна в точности соответствовать первой привязке типа. При этом недопустимы нетривиальные преобразования и расширения типа. Тривиальными считаются, например, преобразования при присваивании изменяемому объекту какого-то типа значения неизменяемого объекта того же типа. К тривиальным относят также преобразования между типом массива (неизменяемый указатель) и типом соответствующего указателя. Однако преобразования int в unsigned int и наоборот уже нетривиальны (они могут привести к некоторым проблемам).
Пример.
template <class T> T min (T,T); //прототип
void main(){
unsigned u=15;
min(u,128); min(128,u);}
Оба вызова функции в этом примере приводят к ошибке, так как целые константы имеют по правилам языка тип int (именно int, а не const int).
Однако в следующем примере преобразование тривиально:
template <class T> T first(T a[]) { return a[0];}
void main() {
int a[]={9,8,7}, *b=a;
cout<<first(b)<<endl; }
хотя имя массива имеет тип const int, а переменная b - тип int.
Важно отметить, что стандартные и сложные преобразования типа не будут выполняться и с аргументами не параметризованных (явно указанных) типов шаблонной функции.
Каждая функция, сгенерированная по шаблону, обладает своими экземплярами всех статических переменных.
Если в шаблоне предусмотрен аргумент функции в виде указателя на параметризованный тип, то фактический аргумент в вызове должен также должен иметь тип указателя.
Шаблонная функция может перегружаться при условии, что список формальных параметров каждого ее варианта отличается от остальных либо типами параметров, либо их числом:
template <class T> T f(T,T);
template <class T> T f(T);
template <class T> T f(int, T);
Для некоторых сочетаний типов аргументов функции использование шаблона для конкретизации может быть заблокировано, если описать функцию с явно заданными типами аргументов (специализированную). То есть, специализированная функция имеет более высокий приоритет при разрешении вызова, чем шаблонная.
Рассмотрим порядок, в котором отыскивается нужный вызов функции, если она определена с возможностью перегрузки как шаблонными, так и специализированными функциями.
Пример.
template <class T> T f(T,T); //объявление шаблона
double f(double,double); //объявление специализированной функции
void main(){
int i;
unsigned int u;
double x,y
f(0,i); // первый вызов
f(0,u); // второй вызов
f(x,y); // третий вызов }
Первый вызов разрешается по правилу II, так как оба аргумента имеют тип int.
Второй вызов был бы неразрешим, если бы не была бы определена специализированная функция. Здесь работает правило III.
В третьем вызове срабатывает правило I и сразу вызывается специализированная функция, так как сопоставление аргументов точное.
Функции, конкретизированные при взятии адреса шаблонной функции, выступают в роли специализированных. Поэтому, если в рассмотренном примере добавить описание
long (*pf)(long,long)=f;
то при компиляции появится сообщение об ошибке, так как во втором случае компилятор не будет знать, какую из функций вызвать по правилу III: либо с аргументами double, либо с аргументами long. Поэтому будет выдано сообщение об ошибке:
Ambiguity between 'f(double,double)' and 'f(long,long)'.
Причем ошибка бы осталась, даже если бы специализированная функция возвращала бы значение типа, отличного от double или не возвращала бы ничего.
ЗАДАНИЕ 1
template <class T, class T> T f(T x);
template <class T1, T2> void f(T1 x);
template <class T> T f(int x);
inline template <class T> T f(T x, T y);
max('a','1'), max(12,17), max("Hello","World").
Объяснить в каждом случае, какое правило использовалось для реализации вызова.
Шаблоны классов (class templates) являются одним из механизмов С++, реализующих в программах полиморфизм. Их называют также "параметризованными типами" (parameterisized types), "генераторами классов" (class generators) или "родовыми классами" (generic classes).
Шаблоны классов позволяют определить структуру семейства классов, по которой в дальнейшем, основываясь на задаваемых параметрах настройки, компилятор создает конкретные экземпляры классов.
Всякое определение или предварительное объявление шаблонного класса начинается со служебного слова template (шаблон). За ним в угловых скобках следует непустой список формальных параметров шаблона. В отличие от шаблонов функций (!) список параметров может содержать не только параметры-типы, но и константные выражения, адреса и функции с внешним связыванием.
Чтобы подчеркнуть, что данный параметр обозначает тип, соответствующий элемент списка должен начинаться со служебного слова class.
Дополнительные параметры шаблона (не типы) могут использоваться, например, при инициализации объектов класса (в конструкторе) или для других целей.
Пример 1.
В описании класса Test1 параметры T1 и T2 обозначают типы (базовые или созданные пользователем), которые будут конкретизированы при создании экземпляра класса:
template <class T1, class T2> class Test1{
...};
Пример 2.
template <class T1, class T2, int size> class Test3;
template <class T, int S> class Test{
int i;
T* pT;
public:
Test(){ i=S; pT=new T; } // конструктор использует параметры шаблона
//функции, использующие имя Test как спецификацию типа
Test(Test<T,S>&); // прототип конструктора копирования
friend ostream& operator<<(ostream& os,Test<T,S>& t)
{ return os<<t.S<<endl;}
};
Если реализация функции-члена вынесена за пределы описания класса, то компилятору необходима информация о параметрах шаблона. Поэтому описание такой функции-члена должно предваряться признаком шаблона. Например, конструктор копирования описывается следующим образом:
template <class T,int S>
inline Test<T,S>::Test(Test& t)
{ ... }
(в данном случае предполагается реализация конструктора как inline-функции).
В описании конструктора имя Test один раз использовано как имя функции и дважды как имя типа:
Если необходимо создать шаблон некоторой глобальной функции, в котором участвует шаблон класса, то это возможно лишь в том случае, когда все параметры шаблона класса являются типами.
Например, ошибкой будет следующее описание шаблона функции foo:
template <class T,int S> void foo(Test<T,S>& t) { ... }
Однако, если шаблон класса Test не имел бы параметра S, то конструкция шаблона функции была бы допустимой.
Замечание.
Шаблон класса можно объявить дружественным некоторому классу. Тогда при конкретизации любая функция-член, сгенерированная по шаблону класса, будет дружественной этому классу.
Вне определения шаблона можно использовать только конкретизации шаблонного класса (то есть, с конкретными значениями параметров):
void f(Test<int,2> a){
...
Test<float,1> *pfl;
... }
В шаблоне глобальной функции можно указывать либо конкретный, либо параметризованный экземпляр шаблонного класса:
// объявление шаблона функции
template <class Type> void f(Test<Type>&, Test<int>&);
Когда выполняется конкретизация шаблона класса, компилятор создает полный набор функций-членов, работающих с конкретными типами данных.
Каждый класс, сгенерированный по шаблону, обладает своими экземплярами статических членов.
Пример.
template <class T> class Test4{
static T a;
static int i;
...
}
Каждый сгенерированный с помощью шаблона класс будет иметь свои собственные статические члены a и i.
Шаблоны классов могут включать объявление друзей. Дружественная функция, спецификация которой не использует тип данного класса-шаблона, будет дружественной для всех реализаций класса. Если тип используется, то функция будет дружественной только для каждого конкретного экземпляра класса.
Пример.
template <class Type> class Test{
...
friend void f(int); // дружественная для всех реализаций
friend void f1(Test<Type>);
};
ЗАДАНИЕ 2