Шаблони C + +

Шаблони ( англ. template ) - Засіб мови C + +, призначене для кодування узагальнених алгоритмів, без прив'язки до деяких параметрах (наприклад, типам даних, розмірами буферів, значенням за замовчуванням).

В C + + можливе створення шаблонів функцій і класів.

Шаблони дозволяють створювати параметризрвані класи та функції. Параметром може бути будь-який тип або значення одного з допустимих типів (ціле число, enum, покажчик на будь-який об'єкт з глобально доступним ім'ям). Наприклад, нам потрібен якийсь клас:

 class  SomeClass  {  int  SomeValue  ;  int  SomeArray  [  20  ]  ;  ...  } 

Для однієї конкретної мети ми можемо використовувати цей клас. Але, раптом, мета трохи змінилася, і потрібен ще один клас. Тепер потрібно 30 елементів масиву SomeArray і речовинний тип SomeValue і елементів SomeArray. Тоді ми можемо абстрагуватися від конкретних типів і використовувати шаблони з параметрами. Синтаксис: на початку перед оголошенням класу напишемо слово template і вкажемо параметри в кутових дужках. У нашому прикладі:

 template  <  int  ArrayLength,  typename  SomeValueType  >  class  SomeClass  {  SomeValueType SomeValue  ;  SomeValueType SomeArray  [  ArrayLength  ]  ;  ...  } 

Тоді для першої моделі пишемо:

 SomeClass  <  20  ,  int  >  SomeVariable  ; 

для другої:

 SomeClass  <  30  ,  double  >  SomeVariable2  ; 

Хоча шаблони надають коротку форму запису ділянки коду, насправді їх використання не скорочує виконані код, так як для кожного набору параметрів компілятор створює окремий екземпляр функції або класу.


1. Шаблони функцій

1.1. Синтаксис опису шаблону

Шаблон функції починається з ключового слова template, за яким в кутових дужках слід список параметрів. Потім слід оголошення функції:

 template  <  typename  T  >  void  sort  (  T array  [  ]  ,  int  size  )  ;  / / Прототип: шаблон sort оголошений, але не визначений  template  <  typename  T  >  void  sort  (  T array  [  ]  ,  int  size  )  / / Оголошення та визначення  {  T t  ;  for  (  int  i  =  0  ;  i  <  size  -  1  ;  i  + +  )  for  (  int  j  =  size  -  1  ;  j  >  i  ;  j  -  )  if  (  array  [  j  ]  <  array  [  j  -  1  ]  )  {  t  =  array  [  j  ]  ;  array  [  j  ]  =  array  [  j  -  1  ]  ;  array  [  j  -  1  ]  =  t  ;  }  }  template  <  int  BufferSize  >  / / Цілочисельний параметр  char  *  read  (  )  {  char  *  Buffer  =  new  char  [  BufferSize  ]  ;  / * Зчитування даних * /  return  Buffer  ;  } 

Ключове слово typename з'явилося порівняно недавно, тому стандарт [1] допускає використання class замість typename :

 template  <  class  T  > 

Замість T допустимо будь-який інший ідентифікатор.


1.2. Приклад використання

Найпростішим прикладом служить визначення мінімуму з двох величин.

Якщо a менше b то повернути а, інакше - повернути b

У відсутність шаблонів програмісту доводиться писати окремі функції для кожного використовуваного типу даних. Хоча багато мов програмування визначають вбудовану функцію мінімуму для елементарних типів (таких як цілі і речові числа), така функція може знадобитися і для складних (наприклад "час" або "рядок") і дуже складних ("гравець" в онлайн-грі) об'єктів.

Так виглядає шаблон функції визначення мінімуму:

 template  <  typename  T  >  T min  (  T a, T b  )  {  return  a  <  b  ?  a  :  b  ;  } 

Для виклику цієї функції можна просто використовувати її ім'я:

 min  (  1  ,  2  )  ;  min  (  'A'  ,  'B'  )  ;  min  (  string  (  "Abc"  )  , String  (  "Cde"  )  )  ; 

1.3. Виклик шаблонної функції

Взагалі кажучи, для виклику шаблонної функції, необхідно вказати значення для всіх параметрів шаблону. Для цього після імені шаблону вказується список значень в кутових дужках:

 int  i  [  5  ]  =  {  5  ,  4  ,  3  ,  2  ,  1  }  ;  sort  <  int  >  (  i,  5  )  ;  char  c  [  ]  =  "Бвгда"  ;  sort  <  char  >  (  c,  strlen  (  c  )  )  ;  sort  <  int  >  (  c,  5  )  ;  / / Помилка: у sort  параметр int [] а не char []  char  *  ReadString  =  read  <  20  >  (  )  ;  delete  [  ]  ReadString  ;  ReadString  =  read  <  30  >  (  )  ; 

Для кожного набору параметрів компілятор генерує новий екземпляр функції. Процес створення нового екземпляра називається інстанцірованіем шаблону.

У прикладі вище компілятор створив дві спеціалізації шаблону функції sort (для типів char і int) і дві спеціалізації шаблону read (для значень BufferSize 20 і 30). Останнє швидше за все марнотратно, оскільки для кожного можливого значення параметра компілятор буде створювати нові і нові екземпляри функцій, які будуть відрізнятися лише однієї константою.


1.3.1. Виведення значень параметрів

У деяких випадках компілятор може сам вивести (логічно визначити) значення параметра шаблона функції з аргументу функції. Наприклад, при виклику вищеописаної функції sort необов'язково вказувати параметр шаблону (якщо він співпадає з типом елементів аргументу-масиву):

 int  i  [  5  ]  =  {  5  ,  4  ,  3  ,  2  ,  1  }  ;  sort  (  i, i  +  5  )  ;  / / Викликається sort   char  c  [  ]  =  "Бвгда"  ;  sort  (  c, c  +  strlen  (  c  )  )  ;  / / Викликається sort  

Можливо виведення і в більш складних випадках.

У разі використання шаблонів класів з цілими параметрами також можливо виведення цих параметрів. Наприклад:

 template  <  int  size  >  class  IntegerArray  {  int  Array  [  size  ]  ;  / * ... * /  }  ; 
 template  <  int  size  >  / / Прототип шаблону  void  PrintArray  (  IntegerArray  <  size  >  array  )  {  / * ... * /  }  / / Виклик шаблону  / / Використання об'єкта шаблону  IntegerArray  <  20  >  ia  ;  PrintArray  (  ia  )  ; 

Правила виведення введені в мову для полегшення використання шаблону і для уникнення можливих помилок, наприклад спроба використання sort< int > для сортування масиву символів.

Якщо параметр шаблону можна вивести за декількома аргументам, то результат виведення повинен бути в точності однаковий для всіх цих аргументів. Наприклад, наступні виклики помилкові:

 min  (  0  ,  'A'  )  ;  min  (  7  ,  7.0  )  ; 

1.4. Помилки в шаблонах

Деякі помилки в описі шаблону можуть бути виявлені вже в місці опису. Ці помилки не залежать від конкретних параметрів. Наприклад:

 template  <  class  T  >  void  f  (  T data  )  {  T  *  pt  =  7  ;  / / Помилка: ініціалізація покажчика цілим числом  datA  =  0  ;  / / Помилка: невідомий ідентифікатор datA  *  pt  =  data  / / Помилка: немає крапки з комою  } 

Помилки, пов'язані з використанням конкретних параметрів шаблону, можна виявити до того, як шаблон використаний. Наприклад, шаблон min сам по собі не містить помилок, однак використання його з типами, для яких операція '<' не визначена, призведе до помилки:

 struct  A  {  int  a  ;  }  ; 
 A obj1, obj2  ;  min  (  obj1, obj2  )  ; 

Якщо ввести операцію '<' до першого використання шаблону, то помилка буде усунена. Так проявляється гнучкість шаблонів у C + + :

 friend  inline  bool  operator  <  (  const  A  &  a1,  const  A  &  a2  )  {  return  a1.  a  <  a2.  a  ;  }  min  (  obj1, obj2  )  ; 

2. Шаблони класів

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

 template  <  class  T  >  class  List  {  / * ... * /  public  :  void  Add  (  const  T  &  Element  )  ;  bool  Find  (  const  T  &  Element  )  ;  / * ... * /  }  ; 

2.1. Використання шаблонів

Для використання шаблону класу, необхідно вказати його параметри:

 List  <  int  >  li  ;  List  <  string  >  ls  ;  li.  Add  (  17  )  ;  ls.  Add  (  "Hello!"  )  ; 

3. Технічні подробиці

3.1. Параметри шаблонів

Параметрами шаблонів можуть бути: параметри-типи, параметри звичайних типів, параметри-шаблони.

Для параметрів будь-якого типу можна вказувати значення за замовчуванням.

 template  <  class  T1,  / / Параметр-тип  typename  T2,  / / Параметр-тип  int  I,  / / Параметр звичайного типу  T1 DefaultValue,  / / Параметр звичайного типу  template  <  class  >  class  T3,  / / Параметр-шаблон  class  Character  =  char  / / Параметр за замовчуванням  > 

3.1.1. Параметри-шаблони

Якщо в шаблоні класу або функції необхідно використовувати один і той самий шаблон, але з різними параметрами, то використовуються параметри-шаблони. Наприклад:

 template  <  class  Type,  template  <  class  >  class  Container  >  class  CrossReferences  {  Container  <  Type  >  mems  ;  Container  <  Type  *  >  refs  ;  / * ... * /  }  ;  CrossReferences  <  Date, vector  >  cr1  ;  CrossReferences  <  string, set  >  cr2  ; 

Не можна використовувати шаблони функцій як параметрів-шаблонів.


3.2. Правила виведення аргументів шаблону функції

Для параметрів, які є типами (наприклад параметр T функції sort) можливо виведення, якщо аргумент функції має один з наступних типів:

Тип аргументу Опис
T
const T
volatile T
Сам тип T, можливо з модифікаторами const або volatile.
 template  <  class  T  >  T ReturnMe  (  const  T arg  )  {  return  arg  ;  }  ReturnMe  (  7  )  ;  ReturnMe  (  'A'  )  ; 
T*
T&
T[A]

A - константа
Покажчик, посилання або масив елементів типу T.

Прикладом може служити шаблон функції sort, розглянутий вище

Templ
Templ - ім'я шаблону класу
В якості аргументу, функція вимагає конкретну спеціалізацію деякого шаблону.
 # Include   template  <  class  T  >  void  sort  (  vector  <  T  >  array  )  {  / * Сортування * /  }  vector  <  int  >  i  ;  vector  <  char  >  c  ;  sort  (  i  )  ;  sort  (  c  )  ; 
T (*) (args)
args - якісь аргументи
Покажчик на функцію, яка повертає тип T.
 template  <  class  T  >  T  *  CreateArray  (  T  (  *  GetValue  )  (  )  ,  const  int  size  )  {  T  *  Array  =  new  T  [  size  ]  ;  for  (  int  i  =  0  ;  i  <  size  ;  i  + +  )  Array  [  i  ]  =  GetValue  (  )  ;  return  Array  ;  }  int  GetZero  (  )  {  return  0  ;  }  char  InputChar  (  )  {  char  c  ;  cin  >>  c  ;  return  c  ;  }  int  *  ArrayOfZeros  =  CreateArray  (  GetZero,  20  )  ;  char  *  String  =  CreateArray  (  InputChar,  40  )  ; 
type T::*
T Class::*

type - якийсь тип
Class - якийсь клас
Покажчик на член класу T довільного типу.
Покажчик на член типу T довільного класу.
 class  MyClass  {  public  :  int  a  ;  }  ;  template  <  class  T  >  T  &  IncrementIntegerElement  (  int  T  ::  *  Element, T  &  Object  )  {  Object.  *  Element  +  =  1  ;  return  Object  ;  }  template  <  class  T  >  T IncrementMyClassElement  (  T MyClass  ::  *  Element, MyClass  &  Object  )  {  Object.  *  Element  +  =  1  ;  return  Object.  *  Element  ;  }  MyClass Obj  ;  int  n  ;  n  =  (  IncrementIntegerElement  (  &  MyClass  ::  a  , Obj  )  )  .  a  ;  n  =  IncrementMyClassElement  (  &  MyClass  ::  a  , Obj  )  ; 
type (T::*) (args)
T (Class::*) (args)

type - якийсь тип
Class - якийсь клас
args - якісь аргументи
Покажчик на функцію-член класу T.
Покажчик на функцію-член деякого класу, який повертає тип T.
 class  MyClass  {  public  :  int  a  ;  int  IncrementA  (  )  ;  }  ;  int  MyClass  ::  IncrementA  (  )  {  return  + +  a  ;  }  template  <  class  T  >  T  &  CallIntFunction  (  int  (  T  ::  *  Function  )  (  )  , T  &  Object  )  {  (  Object.  *  Function  )  (  )  ;  return  Object  ;  }  template  <  class  T  >  T CallMyClassFunction  (  T  (  MyClass  ::  *  Function  )  (  )  , MyClass  &  Object  )  {  return  (  Object.  *  Function  )  (  )  ;  }  MyClass Obj  ;  int  n  ;  n  =  (  CallIntFunction  (  &  MyClass  ::  IncrementA  , Obj  )  )  .  a  ;  n  =  CallMyClassFunction  (  &  MyClass  ::  IncrementA  , Obj  )  ; 

3.3. Члени шаблонів класів

Члени шаблону класу є шаблонами, причому з тією ж, що і у шаблона класу, параметризацією. Зокрема це означає, що визначення функцій-членів слід починати з заголовка шаблону:

 template  <  class  T  >  class  A  {  void  f  (  T data  )  ;  void  g  (  void  )  ;  public  :  A  (  )  ;  }  ;  template  <  class  T  >  void  A  <  T  >  ::  f  (  T data  )  ;  template  <  class  T  >  void  A  <  T  >  ::  g  (  void  )  ; 

Усередині області видимості шаблону не потрібно повторювати специфікатор. Це означає, що наприклад A::A() - це конструктор, хоча можна писати і A::A().


3.3.1. Типи як члени класів

Якщо параметром шаблону є клас, у якого є член, який є типом даних, то для використання цього члена, потрібно застосовувати ключове слово typename. Наприклад:

 class  Container  {  public  :  int  array  [  15  ]  ;  typedef  int  *  iterator  ;  / * ... * /  iterator begin  (  )  {  return  array  ;  }  }  ;  template  <  class  C  >  void  f  (  C  &  vector  )  {  C  ::  iterator  i  =  vector.  begin  (  )  ;  / / Помилка  typename  C  ::  iterator  i  =  vector.  begin  (  )  ;  } 

3.3.2. Шаблони як члени класів

Проблеми виникають і з членами-шаблонами. Якщо шаблон, який є членом класу, який в свою чергу є параметром шаблону, використовується в цьому шаблоні і не допускає виведення параметрів, то необхідно використовувати кваліфікатор template :

 class  A  {  / * ... * /  public  :  template  <  class  T  >  T  &  ConvertTo  (  )  ;  template  <  class  T  >  void  ConvertFrom  (  const  T  &  data  )  ;  }  ;  template  <  class  T  >  void  f  (  T Container  )  {  int  i1  =  Container.  template  ConvertTo  <  int  >  (  )  +  1  ;  Container.  ConvertFrom  (  i1  )  ;  / / Кваліфікатор не потрібен  } 

4. Шаблони в інших мовах програмування

Мова Ада володіє механізмами, схожими на шаблони.

Мова D володіє шаблонами, місцями більш потужними, ніж C + +. [2]

В Java 5 був введений схожий механізм - generic.

Delphi підтримує механізм generic, починаючи з версії 2009.

Примітки

  1. Стандарт C + + "Standart fot the C + + Programming Language": ISO / IEC 14882 1998 - mrst.narod.ru / c-cpp /.
  2. Digital Mars: D Programming Language 2.0 - www.digitalmars.com/d/2.0/template-comparison.html (Англ.)

Література

  • Девід Вандевурд, Микола М. Джосаттіс Шаблони C + +: довідник розробника = C + + Templates: The Complete Guide. - М .: "Вильямс", 2003. - С. 544. - ISBN 0-201-73484-2
  • Подбельский В. В. 6.9. Шаблони функцій / / Розділ 6. Функції, покажчики, посилання / / Мова Сі + + / рец. Дадаєв Ю. Г.. - 4. - М .: Фінанси і статистика, 2003. - С. 230-236. - 560 с. - ISBN 5-279-02204-7, УДК 004.438Сі (075.8) ББК 32.973.26-018 1я173