Извиква се дефиницията на шаблон за конкретен тип параметър. Функции на шаблона

17 март 2009 г. в 20:36 ч

Трикове за специализация на шаблони в C ++

  • C ++

Специализацията на шаблоните е една от "сложните" характеристики на езика C ++ и се използва главно при създаване на библиотеки. За съжаление, някои от спецификите на специализацията по шаблони не са добре обхванати в популярните книги на този език. Нещо повече, дори 53-те страници на официалния езиков стандарт ISO, посветени на шаблоните, описват интересни детайли разхвърляно, оставяйки много да се „отгатнете сами – това е очевидно“. Под изрезката се опитах да изложа ясно основните принципи на специализацията на шаблоните и да покажа как тези принципи могат да бъдат използвани при конструирането на магически заклинания.

Здравей свят

Как сме свикнали да използваме шаблони? Използвайте ключовата дума шаблон, последвана от имената в ъглови скоби параметри на шаблонапоследвано от типа и името. За параметрите те също така показват какво представлява: тип (име на тип) или стойност (например int). Типът на самия шаблон може да бъде клас (клас), структура (структура всъщност е клас) или функция (bool foo () и т.н.). Например, най-простият шаблонен клас "A" може да бъде дефиниран така:

След известно време ще искаме нашият клас да работи еднакво за всички типове, но различно за нещо сложно като int. Глупав въпрос, писане на специализация: изглежда същото като реклама, но настроикиние не посочваме шаблона в ъглови скоби, вместо това посочваме конкретни аргументишаблон след името му:

Шаблон<>клас А< int >(); // int тук е аргументът на шаблона
Готово, можете да пишете методи и полета на специална реализация за int. Тази специализация обикновено се нарича завършен(пълна специализация или изрична специализация). За повечето практически задачи не се изисква повече. И ако е необходимо, тогава...

Персонализираният шаблон е нов шаблон

Ако внимателно прочетете стандарта ISO C ++, можете да намерите интересно твърдение: чрез създаване на специализиран клас шаблони, ние създаваме нов шаблонен клас(14.5.4.3). Какво ни дава? Специализиран шаблонен клас може да съдържа методи, полета или декларации за типове, които не са в класа на шаблона, който сме специализирани. Удобно е, когато имате нужда от метод на шаблонен клас, който да работи само за конкретна специализация - достатъчно е да декларирате метода само в тази специализация, компилаторът ще направи останалото:

Специализиран шаблон може да има свои собствени параметри на шаблона

Дяволът, както знаете, е в детайлите. Фактът, че специализираният шаблонен клас е напълно нов и отделен клас със сигурност е интересен, но в това има малко магия. И магията е в незначителна последица - ако е отделен шаблонен клас, значи може да има отделен, по никакъв начин не свързан с неспециализиран шаблонен клас настроики(параметрите са това, което е след шаблона в ъглови скоби). Например, като това:

Шаблон< typename S, typename U >клас А< int > {};
Вярно е, че това е точно кодът на компилатора няма да компилира- не използваме новите параметри на шаблона S и U, което е забранено за специализиран шаблонен клас (но специализираният компилатор разбира, че това е клас, защото има същото име "A" като вече декларирания шаблонен клас). Компилаторът дори ще каже специална грешка: „изричната специализация използва синтаксис на частична специализация, използвайте шаблон<>вместо ". Намеква, че ако няма какво да се каже, тогава трябва да използвате шаблон<>и да не се показва. Тогава защо могат да се използват нови параметри в специализиран клас шаблони? Отговорът е странен - ​​за да попитам аргументиспециализации (аргументите са това, което е след името на класа в ъглови скоби). Тоест, като специализираме шаблонен клас, можем вместо просто и разбираемо int да го специализираме чрез new настроики:

Шаблон< typename S, typename U >клас А< std::map< S, U > > {};
Такъв странен запис ще се компилира. И когато се използва получения клас шаблон с std :: map, ще се използва специализация, при която типът std :: map ще бъде наличен като параметър на новия шаблон S, а типът стойност на std :: map като U.

Такава специализация на шаблона, в която се посочва нов списък с параметри и чрез тези параметри се задават аргументи за специализацията, се нарича частична специализация(частична специализация). Защо "частичен"? Очевидно защото първоначално е бил замислен като синтаксис за специализиране на шаблон не за всички аргументи. Пример, при който шаблонен клас с два параметъра се специализира само в един от тях (специализацията ще работи, когато първият аргумент T е посочен като int. В този случай вторият аргумент може да бъде всякакъв - за това е нов параметър U въведено в частичната специализация и е посочено в списъка с аргументи за специализация):

Шаблон< typename T, typename S >клас B (); шаблон< typename U >клас Б< int, U > {};

Магическите последици от частичната специализация

Има редица интересни последици от горните две свойства на специализацията на шаблоните. Например, когато се използва частична специализация, е възможно чрез въвеждане на нови параметри на шаблона и описване на специализирани аргументи чрез тях да се разделят съставните типове на най-прости. В примера по-долу ще се използва специализираният шаблонен клас A, ако аргументите на шаблона са от типа на указател на функция. В същото време, чрез новите параметри на шаблона S и U, можете да получите типа на връщаната стойност на тази функция и типа на нейния аргумент:

Шаблон< typename S, typename U >клас А< S(*)(U) > {};
И ако декларирате typedef или static const int в специализиран шаблон (като се възползвате от факта, че това е нов шаблон), тогава можете да го използвате, за да извлечете необходимата информация от типа. Например, ние използваме клас шаблон за съхраняване на обекти и искаме да получим размера на предадения обект или 0, ако е указател. В два реда:

Шаблон< typename T >struct Get (const static int Size = sizeof (T);); шаблон< typename S >структура Get< S* >(const static int Size = 0;); Вземи< int >:: Размер // напр. 4 Вземи< int* >:: Размер // 0 - намери указателя :)
Този тип магия се използва главно в библиотеки: stl, boost, loki и т.н. Разбира се, при програмирането на високо ниво използването на подобни трикове е тромаво - мисля, че всеки си спомня конструкцията за получаване на размера на масив :). Но в библиотеките частичната специализация прави относително лесно внедряването на делегати, събития, сложни контейнери и други понякога много необходими и полезни неща.

Колеги, ако откриете грешка (а аз за съжаление не съм гуру - може и да греша) или имате критики, въпроси или допълнения към горното, ще се радвам да коментирате.

Актуализация: Обещано продължение

Функцията за шаблон дефинира общ набор от операции, които ще бъдат приложени към различни типове данни. Използвайки този механизъм, някои общи алгоритми могат да бъдат приложени към широк спектър от данни. Както знаете, много алгоритми са логически еднакви, независимо от типа данни, с които оперират. Например алгоритъмът за бързо сортиране е един и същ както за масив от цели числа, така и за масив от числа с плаваща запетая. Различава се само типът данни, които трябва да се сортират. Създавайки обща функция, можете да дефинирате същността на алгоритъм без оглед на типа данни. След това компилаторът автоматично генерира правилния код за типа данни, за които е създадена тази конкретна реализация на функцията по време на компилиране. По същество, когато се създаде шаблонна функция, се създава функция, която може автоматично да се претовари.

Шаблонните функции се създават с помощта на ключовата дума template. Обичайното значение на думата "шаблон" отразява напълно използването му в C ++. Шаблонът се използва за създаване на скелета на функцията, оставяйки подробностите за реализацията на компилатора. Общата форма на функцията шаблон е както следва:

шаблон return_type function_name (списък с параметри)
{
// тяло на функцията
}

Тук ptyp е параметър за тип, заместител за името на типа данни, което се използва от функцията. Този тип параметър може да се използва в дефиниция на функция. Това обаче е само "заместител", който автоматично ще бъде заменен от компилатора с действителния тип данни при създаване на конкретна версия на функцията.

По-долу е даден кратък пример, който създава шаблонна функция, която има два параметъра. Тази функция променя стойностите на стойностите на тези параметри помежду си. Тъй като общият процес на обмен на стойности между две променливи не зависи от техния тип, той може да бъде естествено реализиран с помощта на функция за шаблон.

// пример за шаблон на функция
#включи
// шаблон на функция
шаблон размяна на празнина (X & a, X & b)
{
Х температура;
температура = a;
а = b;
b = температура;
}
int main ()
{
int i = 10, j = 20;
float x = 10,1, y = 23,3;
char a = "x", b = "z";
cout<< "Original i, j: " << i << " " << j << endl;
cout<< "Original x, y: " << x << " " << у << endl;
cout<< "Original a, b: " << a << " " << b << endl;
размяна (i, j); // разменяме цели числа
размяна (x, y); // обмен на реални стойности
размяна (a, b); // размяна на знаци
cout<< "Swapped i, j: " << i << " " << j << endl;
cout<< "Swapped x, y: " << x << " " << у << endl;
cout<< "Swapped a, b: " << a << " " << b << endl;
връщане на 0;
}

Нека разгледаме по-отблизо тази програма. линия

Шаблон размяна на празнина (X & a, X & b)

Показва на компилатора, че се генерира шаблон. Тук X е типов шаблон, използван като параметър за тип. Следва декларацията на функцията swap (), използваща типа данни X за тези параметри, които ще обменят стойности. Във функцията main () функцията swap () се нарича предаване на три различни типа данни към нея: цели числа, числа с плаваща запетая и знаци. Тъй като функцията swap () е шаблонна функция, компилаторът автоматично ще създаде три различни версии на функцията swap () - една за работа с цели числа, една за работа с числа с плаваща запетая и накрая една за работа с променливи на символ Тип.

Урок 29. Използване на шаблони за функции

При създаването на функции понякога възникват ситуации, когато две функции извършват една и съща обработка, но работят с различни типове данни (например, едната използва параметри от тип int, а другата от тип float). Вече знаете от урок 13, че като използвате механизма за претоварване на функциите, можете да използвате едно и също име за функции, които изпълняват различни действия и имат различни типове параметри. Въпреки това, ако функциите връщат стойности от различни типове, трябва да използвате уникални имена за тях (вижте бележката за урок 13). Например, да предположим, че имате функция с име max, която връща максимума от две цели числа. Ако по-късно се нуждаете от подобна функция, която връща максимума от две стойности с плаваща запетая, трябва да дефинирате друга функция, като fmax. В този урок ще научите как да използвате C++ шаблони за бързо създаване на функции, които връщат различни типове стойности. До края на този урок ще усвоите следните основни понятия:

    Шаблонът дефинира набор от изрази, с които вашите програми могат по-късно да създават множество функции.

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

    Шаблоните за функции имат специфични имена, които съответстват на името на функцията, което използвате във вашата програма.

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

    По време на компилацията компилаторът на C ++ ще създаде функции във вашата програма, използвайки типовете, посочени в прототипите на функциите, които препращат към името на шаблона.

Функционалните шаблони имат уникален синтаксис, който може да е объркващ на пръв поглед. Въпреки това, след като създадете един или два шаблона, ще откриете, че всъщност са много лесни за използване.

СЪЗДАЙТЕ ПРОС ФУНКЦИОНАЛЕН МОДЕЛ

Шаблонът на функцията дефинира независима от типа функция. Използвайки такъв шаблон, вашите програми могат по-късно да дефинират специфични функции с необходимите типове. Например, следното е шаблон за функция с име max, която връща по-голямата от двете стойности:

шаблон T max (T a, T b)

(ако (a> b) връщане (a); иначе връщане (b);)

Буквата T в този случай представлява общ тип шаблон. След като дефинирате шаблон във вашата програма, вие декларирате прототипи на функции за всеки тип, който ви е необходим. В случая на шаблона max, следните прототипи създават функции от тип float и int.

float max (float, float); int max (int, int);

Когато компилаторът на C ++ срещне тези прототипи, той ще замени типа шаблон T с типа, който сте посочили при конструирането на функцията. В случай на типа float, функцията max след замяната ще приеме следната форма:

шаблон T max (T a, T b)

(ако (a> b) връщане (a); иначе връщане (b);)

float max (float a, float b)

(ако (a> b) връщане (a); иначе връщане (b);)

Следващата програма MAX_TEMP.CPP използва шаблона max за създаване на функция от тип int и float.

#включи

шаблон T max (T a, T b)

(ако (a> b) връщане (a); иначе връщане (b);)

float max (float, float);

int max (int, int);

(cout<< "Максимум 100 и 200 равен " << max(100, 200) << endl; cout << "Максимум 5.4321 и 1.2345 равен " << max(5.4321, 1.2345) << endl; }

По време на компилацията компилаторът на C ++ автоматично генерира изрази за конструиране на една функция, която работи с типа int, и втората функция, която работи с типа float. Тъй като C ++ компилаторът управлява изразите, които съответстват на функциите, които създавате с помощта на шаблони, той ви позволява да използвате едни и същи имена за функции, които връщат различни типове стойности. Не можете да направите това, като използвате само претоварване на функции, както е обсъдено в урок 13.

Използване на функционални шаблони

Тъй като вашите програми стават по-сложни, може да има ситуации, в които имате нужда от подобни функции, които изпълняват едни и същи операции, но с различни типове данни. Функционалният шаблон позволява на вашите програми да дефинират обща или независима от типа функция. Когато една програма трябва да използва функция за конкретен тип, като int или float, тя посочва прототип на функцията, който използва името на шаблона на функцията и типовете връщане и параметри. По време на компилацията C ++ ще създаде съответната функция. Чрез създаване на шаблони намалявате броя на функциите, които трябва да кодирате сами, и вашите програми могат да използват едно и също име за функции, които изпълняват конкретна операция, независимо от връщаната стойност на функцията и типовете параметри.

ШАБЛОНИ, КОИТО ИЗПОЛЗВАТ МНОГО ТИПА

Предишната дефиниция на шаблона за функцията max използва единичен общ тип, T. Много често се изискват множество типове в шаблон на функция. Например, следните изрази създават шаблон за функцията show_array, която показва елементите на масив. Шаблонът използва тип T за дефиниране на типа на масива и тип T1, за да посочи типа на параметъра за броене:

шаблон

< count; index++) cout << array << " "; cout << endl; }

Както преди, програмата трябва да посочи прототипи на функциите за необходимите типове:

void show_array (int *, int); void show_array (float *, неподписан);

Следващата програма SHOW_TEM.CPP използва шаблон за създаване на функции, които извеждат масиви от тип int и тип float.

#включи

шаблон void show_array (T * масив, T1 брой)

(Индекс Т1; за (индекс = 0; индекс< count; index++) cout << array “ " "; cout << endl; }

void show_array (int *, int);

void show_array (float *, неподписан);

(int pages = (100, 200, 300, 400, 500); float pricesH = (10.05, 20.10, 30.15); show_array (страници, 5); show_array (цени, 3);)

Шаблони и множество видове

Тъй като функционалните шаблони стават по-сложни, те могат да осигурят поддръжка за множество типове. Например, вашата програма може да създаде шаблон за функция с име array_sort, която сортира елементите на масив. В този случай функцията може да използва два параметъра: първият, съответстващ на масива, и вторият, съответстващ на броя на елементите в масива. Ако програмата приеме, че масивът никога няма да съдържа повече от 32767 стойности, тя може да използва типа int за параметъра за размер на масива. Въпреки това, по-общ модел би могъл да предостави на програмата възможността да посочи свой собствен тип за този параметър, както е показано по-долу:

шаблон T , клас T1> void array_sort (T масив, T1 елементи)

(// оператори)

Използвайки шаблона array_sort, програмата може да създава функции, които сортират малки плаващи числа (по-малко от 128 елемента) и много големи int масиви, използвайки следните прототипи:

void array_sort (float, char); void array_sort (int, long);

КАКВО ТРЯБВА ДА ЗНАЕТЕ

Както вече знаете, използването на шаблони за функции намалява тежестта на кодирането, като позволява на компилатора на C ++ да генерира изрази за функции, които се различават само по типове връщане и параметри. В урок 30 ще научите как да използвате шаблони за създаване на независими от типа или общи класове. Преди да завършите урок 30, уверете се, че сте усвоили следните основни понятия:

      Функционалните шаблони ви позволяват да декларирате независими от типа или общи функции.

      Когато вашата програма трябва да използва функция с определени типове данни, тя трябва да предостави прототип на функцията, който дефинира необходимите типове.

      Когато компилаторът на C ++ срещне такъв прототип на функцията, той ще създаде оператори, съответстващи на тази функция, замествайки необходимите типове.

      Вашите програми трябва да създават шаблони за общи функции, които работят с различни типове. С други думи, ако използвате само един тип с функция, няма нужда да прилагате шаблон.

      Ако функцията изисква няколко типа, шаблонът просто присвоява на всеки тип уникален идентификатор, като T, T1 и T2. По-късно в процеса на компилация, компилаторът на C ++ ще присвои правилно типовете, които сте посочили в прототипа на функцията.

Специализацията на шаблоните е една от "сложните" характеристики на езика C ++ и се използва главно при създаване на библиотеки. За съжаление, някои от спецификите на специализацията по шаблони не са добре обхванати в популярните книги на този език. Нещо повече, дори 53-те страници на официалния езиков стандарт ISO, посветени на шаблоните, описват интересни детайли разхвърляно, оставяйки много да се „отгатнете сами – това е очевидно“. Под изрезката се опитах да изложа ясно основните принципи на специализацията на шаблоните и да покажа как тези принципи могат да бъдат използвани при конструирането на магически заклинания.

Здравей свят

Как сме свикнали да използваме шаблони? Използвайте ключовата дума шаблон, последвана от имената в ъглови скоби параметри на шаблонапоследвано от типа и името. За параметрите те също така показват какво представлява: тип (име на тип) или стойност (например int). Типът на самия шаблон може да бъде клас (клас), структура (структура всъщност е клас) или функция (bool foo () и т.н.). Например, най-простият шаблонен клас "A" може да бъде дефиниран така:

След известно време ще искаме нашият клас да работи еднакво за всички типове, но различно за нещо сложно като int. Глупав въпрос, писане на специализация: изглежда същото като реклама, но настроикиние не посочваме шаблона в ъглови скоби, вместо това посочваме конкретни аргументишаблон след името му:

Шаблон<>клас А< int >(); // int тук е аргументът на шаблона
Готово, можете да пишете методи и полета на специална реализация за int. Тази специализация обикновено се нарича завършен(пълна специализация или изрична специализация). За повечето практически задачи не се изисква повече. И ако е необходимо, тогава...

Персонализираният шаблон е нов шаблон

Ако внимателно прочетете стандарта ISO C ++, можете да намерите интересно твърдение: чрез създаване на специализиран клас шаблони, ние създаваме нов шаблонен клас(14.5.4.3). Какво ни дава? Специализиран шаблонен клас може да съдържа методи, полета или декларации за типове, които не са в класа на шаблона, който сме специализирани. Удобно е, когато имате нужда от метод на шаблонен клас, който да работи само за конкретна специализация - достатъчно е да декларирате метода само в тази специализация, компилаторът ще направи останалото:

Специализиран шаблон може да има свои собствени параметри на шаблона

Дяволът, както знаете, е в детайлите. Фактът, че специализираният шаблонен клас е напълно нов и отделен клас със сигурност е интересен, но в това има малко магия. И магията е в незначителна последица - ако е отделен шаблонен клас, значи може да има отделен, по никакъв начин не свързан с неспециализиран шаблонен клас настроики(параметрите са това, което е след шаблона в ъглови скоби). Например, като това:

Шаблон< typename S, typename U >клас А< int > {};
Вярно е, че това е точно кодът на компилатора няма да компилира- не използваме новите параметри на шаблона S и U, което е забранено за специализиран шаблонен клас (но специализираният компилатор разбира, че това е клас, защото има същото име "A" като вече декларирания шаблонен клас). Компилаторът дори ще каже специална грешка: „изричната специализация използва синтаксис на частична специализация, използвайте шаблон<>вместо ". Намеква, че ако няма какво да се каже, тогава трябва да използвате шаблон<>и да не се показва. Тогава защо могат да се използват нови параметри в специализиран клас шаблони? Отговорът е странен - ​​за да попитам аргументиспециализации (аргументите са това, което е след името на класа в ъглови скоби). Тоест, като специализираме шаблонен клас, можем вместо просто и разбираемо int да го специализираме чрез new настроики:

Шаблон< typename S, typename U >клас А< std::map< S, U > > {};
Такъв странен запис ще се компилира. И когато се използва получения клас шаблон с std :: map, ще се използва специализация, при която типът std :: map ще бъде наличен като параметър на новия шаблон S, а типът стойност на std :: map като U.

Такава специализация на шаблона, в която се посочва нов списък с параметри и чрез тези параметри се задават аргументи за специализацията, се нарича частична специализация(частична специализация). Защо "частичен"? Очевидно защото първоначално е бил замислен като синтаксис за специализиране на шаблон не за всички аргументи. Пример, при който шаблонен клас с два параметъра се специализира само в един от тях (специализацията ще работи, когато първият аргумент T е посочен като int. В този случай вторият аргумент може да бъде всякакъв - за това е нов параметър U въведено в частичната специализация и е посочено в списъка с аргументи за специализация):

Шаблон< typename T, typename S >клас B (); шаблон< typename U >клас Б< int, U > {};

Магическите последици от частичната специализация

Има редица интересни последици от горните две свойства на специализацията на шаблоните. Например, когато се използва частична специализация, е възможно чрез въвеждане на нови параметри на шаблона и описване на специализирани аргументи чрез тях да се разделят съставните типове на най-прости. В примера по-долу ще се използва специализираният шаблонен клас A, ако аргументите на шаблона са от типа на указател на функция. В същото време, чрез новите параметри на шаблона S и U, можете да получите типа на връщаната стойност на тази функция и типа на нейния аргумент:

Шаблон< typename S, typename U >клас А< S(*)(U) > {};
И ако декларирате typedef или static const int в специализиран шаблон (като се възползвате от факта, че това е нов шаблон), тогава можете да го използвате, за да извлечете необходимата информация от типа. Например, ние използваме клас шаблон за съхраняване на обекти и искаме да получим размера на предадения обект или 0, ако е указател. В два реда:

Шаблон< typename T >struct Get (const static int Size = sizeof (T);); шаблон< typename S >структура Get< S* >(const static int Size = 0;); Вземи< int >:: Размер // напр. 4 Вземи< int* >:: Размер // 0 - намери указателя :)
Този тип магия се използва главно в библиотеки: stl, boost, loki и т.н. Разбира се, при програмирането на високо ниво използването на подобни трикове е тромаво - мисля, че всеки си спомня конструкцията за получаване на размера на масив :). Но в библиотеките частичната специализация прави относително лесно внедряването на делегати, събития, сложни контейнери и други понякога много необходими и полезни неща.

Колеги, ако откриете грешка (а аз за съжаление не съм гуру - може и да греша) или имате критики, въпроси или допълнения към горното, ще се радвам да коментирате.

Актуализация: Обещано продължение