You are on page 1of 22

Поліморфізм

Лекція 11
Шаблони функцій

1
План лекції
1. Узагальнене програмування
2. Шаблони функцій
3. Використання шаблонів для опису функцій-
членів за межами оголошень
параметризованих класів
4. Використання шаблону для опису
узагальненого алгоритму
5. Перевантаження шаблонів функцій
6. Особливості використання шаблонів функцій
7. Параметри умовчання для шаблонів

2
Узагальнене програмування
Шаблони забезпечують простий спосіб
уведення різного роду загальних концепцій і
прості методи їх спільного використання.
Отримані в результаті класи і функції за
часом виконання і вимогам до пам’яті сумірні
з написаним вручну кодом.
Шаблони забезпечують безпосередню
підтримку узагальненого програмування,
тобто програмування з використанням типу в
якості параметра
Шаблони функцій
Синтаксис оголошення шаблонної функції
наступний:
template <class T>
тип ідентифікатор([список_параметрів]){
реалізація }
Використання:
Для опису узагальненого алгоритму.
Для реалізації загального підходу використання
об’єктів (можуть бути і шаблони).
Для опису функцій-членів параметризованих
класів.
Функції-члени, визначені за межами опису класу,
оформлені як окремі шаблонні функції

У середині опису класу назва класу вказана


без специфікатора <T>
За межами опису класу назва класу вказана з
використанням специфікатора <T>
Опис узагальненого алгоритму
Порівняння використання макрокоманди і шаблону для
опису одного алгоритму – копіювання вмісту одного масиву
в другий. Спочатку – макрокоманда
#define COPY(A,B,N)
{for(int i=0; i<N; i++)A[i]=B[i];}
тут відсутня можливість перевірки типів компілятором.
Опис шаблонної функції:
template <class T>
void copy (T a[], T b[], int n){
for (int i=0; i<n; i++) a[i]=b[i]; }
Використання copy() не викличе помилку на етапі
компіляції тільки за умови, якщо типи обох масивів
співпадатимуть.
Наприклад:
double a1[10], a2[10]; int b[10];
copy(a1,a2,10); // нормально, відповідає шаблону
copy(a1,b,10); // помилка! не відповідає шаблону
Виведення типу аргументів шаблону функції

Функція може бути інстаційована неявно за типами


фактичних параметрів у момент виклику. У розглянутому
раніше прикладі можна було виконати явне інстанціювання і
записати виклик функції у такій формі:
copy<double>(a1,a2,10);
але ми написали просто
copy(a1,a2,10);
і компілятор сам за типом параметрів «зрозумів» яким типом
ми планували інстанціювати цю функцію.
Цей процес називається виведенням (deduction) типу
аргументів шаблону функції за типами фактичних параметрів.
Вимоги до фактичного параметру-типу
Фактичний параметр-тип, який буде використаний для
інстанціювання, повинен забезпечувати очікувану відкриту
поведінку, тобто використані у шаблонній функції операції
мають бути для нього визначеними.
Наприклад, у шаблонній функції сортування:
template <class T>void sort (T a[], int n){
. . .
if (a[i]<a[j]){
T temp=a[j]; a[j]=a[i];
a[i]=temp; }
. . .
}
були використані оператори порівняння ”<” та присвоєння
”=”, які визначені не для кожного типу.
Як можна гарантувати, що фактичний параметр-тип
зможе забезпечити очікувану поведінку?
1) Впевнитися, що для такого типу перевантажені
потрібні операції (порівняння і присвоєння у
розглянутому прикладі), якщо це вбудований тип.
Якщо користувацький, то перевантажити потрібні
операції.
2) Визначити окрему реалізацію шаблонної функції
для типу, в якому потрібні операції не визначені.
3) Якщо функція досить велика, то замінити
«сумнівні» операції маленькими функціями.
Залишити єдиний шаблон загальної функції, яка
викликатиме ці маленькі, а уже маленькі
перевантажити для різних типів по-різному.
Для користувацьких типів –потрібні операції визначаємо самі
Використання маленьких перевантажених
функцій
Для розглянутого прикладу можна визначити дві
шаблонні функції – обміну:
template <class T>void swap(T &x, T &y){
T temp; temp=x; x=y; y=temp;}
та порівняння:
template <class T>int compare(T &x, T &y){
return x<y;}
Тоді їх виклик можна вставити у функцію сортування
наприклад так:
template <class T>void sort (T a[], int n){
. . .
if (compare(a[i],a[j]))swap(a[i],a[j]);
. . .
}
Для більшості типів розглянуті реалізації цих функцій
цілком припустимі, але наприклад для рядків,
представлених як масиви символів – ні. І тоді потрібно
робити окремі реалізації нешаблонних функцій,
наприклад:
int compare(char*s1, char*s2){
return strcmp(s1,s2)<0;}
void swap( char * s1, char* s2) {
int max_size;
max_size=(strlen(s1)>=strlen(s2))?
strlen(s1): strlen(s2);
char*temp=new char[max_size+1];
strcpy(temp,s1); strcpy(s1,s2);
strcpy(s2,temp);
delete[] temp;
}
І вставити ці реалізації у список перевантажених функцій
разом з шаблонними.
Перевантаження шаблонних функцій
Шаблонні функції можуть бути перевантажені, при
цьому до списку перевантажених можна включати як
шаблонні, так і звичайні – не шаблонні функції.
Механізм дозволу перевантаження наступний.
За рівних умов пріоритет матиме нешаблонна функція.
Для шаблонних функцій – та, яка б найкраще
відповідала списку аргументів (сигнатурі).
У випадку вибору між більше й менше спеціалізованими
шаблонами, перевага надається більш спеціалізованому.
Якщо не знайдено потрібних шаблонів, то аналізує
нешаблонні функції з використанням підвищення,
перетворень типів.
Приклад
Задані такі функції
template <class T> T sqrt(T);(1)
template <class T> complex<T> sqrt(complex<T>);(2)
double sqrt(double);(3)
complex<double> z;
Яка буде обрана реалізація:
sqrt(2.0); → (3) sqrt(double)
sqrt(2); → (1) sqrt<int>(int)
sqrt(z); → (2) sqrt<double>(complex<double>)
Варіанти вибору для останнього випадку:
sqrt<double>(complex<double>) і
sqrt<complex<double>>(complex<double>).
Зауваження:
Якщо жодної відповідності не знайдено,
виклик вважається помилковим.
Якщо існує два або більше однаково
підходящих варіанти, виклик вважається
неоднозначним і це також помилка.
Ніяких підвищень типу, стандартних
перетворень тощо для шаблонних функцій
не існує, тільки точна відповідність
Особливості використання шаблонів функцій
У шаблоні функції, як і у шаблоні класу,
може бути декілька аргументів.
Існує можливість виведення типу
аргументів шаблону функції за типами
аргументів під час її виклику.
Аргументи можна використати для вибору
алгоритму, якщо певний нюанс поведінки
не повинен залежати від типу, яким функція
буде інстанційована.
Використання параметру шаблону
для вибору варіанту реалізації алгоритму
Для розглянутого прикладу можна передбачити
додаткове узагальнення: можливість сортувати з
врахуванням регістру або без врахування регістру.
Тобто тип елементів однаковий, а вибір алгоритму
залежить від конкретної ситуації.
Очікувана реалізація такої функції:
template <class T, class C>
void sort (T&a[], int n){
. . .
if C::less(T a[i], T a[j])
. . .
}
Тоді потрібно визначити наступні класи:
template <class T> class Ignore{
public:
static bool less (Tx, Ty){
return strcmpi(x,y)<0; //ігнорувати регистр
}
};
template <class T> class Account{
public:
static bool less(Tx, Ty){
return strcmp(x,y)<0; //врахувати регистр
}
};
Для вибору правила порівняння, аргументи шаблону задані явно:
void f(){
char p[100];
char *a[10];
void sort <char, Ignore <char>>(p, 100);
void sort <char*, Account <char*>>(a, 4);}
Параметри умовчання для шаблонів
Щоб критерій був явно вказаний тільки для незвичайних
ситуацій, можна використати механізм перевантаження:
template <class T, class C> void f(T*, int){};
template <class T > void f(T*, int){};
або вказати “звичайний” варіант як аргумент шаблону за
умовчанням:
template <class T, class C = Account<T>>
void f(T*, int){};
Тепер виклик f(x,10) тотожно
f<char*, Account<char*>>(x,10)
а для використання Ignore потрібний явний виклик
f<char*, Ignore <char*>>(x,10)

You might also like