Шаблоны C++Шабло́ны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию). В C++ возможно создание шаблонов функций и классов. Шаблоны позволяют создавать параметризованные классы и функции. Параметром может быть любой тип или значение одного из допустимых типов (целое число, enum, указатель на любой объект с глобально доступным именем, ссылка). Например, нам нужен какой-то класс: class SomeClass{
int SomeValue;
int SomeArray[20];
...
};
Для одной конкретной цели мы можем использовать этот класс. Но, вдруг, цель немного изменилась, и нужен еще один класс. Теперь нужно 30 элементов массива template < int ArrayLength, typename SomeValueType > class SomeClass{
SomeValueType SomeValue;
SomeValueType SomeArray[ ArrayLength ];
...
};
Тогда для первого случая (с целочисленным SomeClass < 20, int > SomeVariable;
для второго: SomeClass < 30, double > SomeVariable2;
Хотя шаблоны предоставляют краткую форму записи участка исходного кода, их использование не сокращает исполняемый код, так как для каждого набора параметров компилятор создаёт отдельный экземпляр функции или класса. Как следствие, исчезает возможность совместного использования скомпилированного кода в рамках разделяемых библиотек. Шаблоны функцийСинтаксис описания шаблонаШаблон функции начинается с ключевого слова 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;
}
Ключевое слово template< class T >
Вместо T допустим любой другой идентификатор. Пример использованияПростейшим примером служит определение минимума из двух величин. Если 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" ) );
Вызов шаблонной функцииВообще говоря, для вызова шаблонной функции необходимо указать значения для всех параметров шаблона. Для этого после имени шаблона указывается список значений в угловых скобках: int i[] = { 5, 4, 3, 2, 1 };
sort<int>(i, 5);
char c[] = "бвгда";
sort<char>( c, strlen( c ) );
sort< int >(c, 5); // ошибка: у sort<int> параметр int[], а не char[]
char *ReadString = read<20>();
delete[] ReadString;
ReadString = read<30>();
Для каждого набора параметров компилятор генерирует новый экземпляр функции. Процесс создания нового экземпляра называется инстанцированием шаблона. В примере выше компилятор создал две специализации шаблона функции Выведение значений параметровВ некоторых случаях компилятор может сам вывести (логически определить) значение параметра шаблона функции из аргумента функции. Например, при вызове вышеописанной функции int i[5] = { 5, 4, 3, 2, 1 };
sort( i, 5 ); // вызывается sort< int >
char c[] = "бвгда";
sort( c, strlen( c ) ); // вызывается sort< char >
Возможно выведение и в более сложных случаях. В случае использования шаблонов классов с целыми параметрами также возможно выведение этих параметров. Например: template< int size >
class IntegerArray
{
int Array[ size ];
/* ... */
};
template< int size > // Прототип шаблона
void PrintArray( IntegerArray< size > array ) { /* ... */ } // Вызов шаблона
// Использование объекта шаблона
IntegerArray<20> ia;
PrintArray( ia );
Правила выведения введены в язык для облегчения использования шаблона и для избежания возможных ошибок, например попытки использования Если параметр шаблона можно вывести по нескольким аргументам, то результат выведения должен быть в точности одинаков для всех этих аргументов. Например, следующие вызовы ошибочны: min (0, 'a');
min (7, 7.0);
Ошибки в шаблонахОшибки, связанные с использованием конкретных параметров шаблона, нельзя выявить до того, как шаблон использован. Например, шаблон struct A
{
int a;
};
A obj1, obj2;
min( obj1, obj2 );
Если ввести операцию friend inline bool operator< ( const A& a1, const A& a2 ) { return a1.a < a2.a; }
min( obj1, obj2 );
Шаблоны классовВ классе, реализующем связный список целых чисел, алгоритмы добавления нового элемента списка, поиска нужного элемента не зависят от того, что элементы списка — целые числа. Те же алгоритмы применялись бы и для списка символов, строк, дат, классов игроков и так далее. template< class T >
class List
{
/* ... */
public:
void Add( const T& Element );
bool Find( const T& Element );
/* ... */
};
Использование шаблоновДля использования шаблона класса необходимо указать его параметры: List<int> li;
List<string> ls;
li.Add( 17 );
ls.Add( "Hello!" );
Технические подробностиПараметры шаблоновПараметрами шаблонов могут быть: параметры-типы, параметры обычных типов, параметры-шаблоны. Для параметров любого типа можно указывать значения по умолчанию. template< class T1, // параметр-тип
typename T2, // параметр-тип
int I, // параметр обычного типа
T1 DefaultValue, // параметр обычного типа
template< class > class T3, // параметр-шаблон
class Character = char // параметр по умолчанию
>
Параметры-шаблоныЕсли в шаблоне класса или функции необходимо использовать один и тот же шаблон, но с разными параметрами, то используются параметры-шаблоны. Например: template< class Type, template< class > class Container >
class CrossReferences
{
Container< Type > mems;
Container< Type* > refs;
/* ... */
};
CrossReferences< Date, vector > cr1;
CrossReferences< string, set > cr2;
Нельзя использовать шаблоны функций в качестве параметров-шаблонов. Правила выведения аргументов шаблона функцииДля параметров, которые являются типами (например, параметр T функции sort) возможно выведение, если аргумент функции имеет один из следующих типов:
Члены классов-шаблоновЧлены шаблона класса являются шаблонами, причём с той же, что и у шаблона класса, параметризацией. В частности это означает, что определение функций-членов следует начинать с заголовка шаблона: 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 );
Внутри области видимости шаблона не нужно повторять спецификатор. Это значит, что например Типы как члены классовЕсли параметром шаблона является класс, у которого есть член, являющийся типом данных, то для использования этого члена, нужно применять ключевое слово 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();
}
Шаблоны как члены классовПроблемы возникают и с членами-шаблонами. Если шаблон (ConvertTo()), который является членом класса (A), который в свою очередь является параметром шаблона (f), используется в этом шаблоне (f) и не допускает выведения параметров, то необходимо использовать квалификатор 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 ); // квалификатор не нужен
}
Критика и сравнение с альтернативамиШаблонное метапрограммирование в C++ страдает от множества ограничений, включая проблемы портируемости, отсутствие поддержки отладки или ввода/вывода в процессе инстанцирования шаблонов, длительное время компиляции, низкую читабельность кода, скудную диагностику ошибок и малопонятные сообщения об ошибках[2]. Подсистема шаблонов C++ определяется как полный по Тьюрингу чистый функциональный язык программирования, но программисты в функциональном стиле считают это провокацией и не спешат признавать C++ успешным языком[3]. Многие языки (Java 5, Ада, Delphi 2009) реализуют поддержку обобщённого программирования более простым способом, некоторые даже на уровне системы типов (см. Eiffel, а также параметрический полиморфизм в языках семейства ML); такие языки не нуждаются в механизмах, похожих на шаблоны C++. Средства макроподстановки в Си, хоть и не обладают Тьюринг-полнотой, представляют собой достаточное для низкоуровневого программирования средство порождающего программирования, и в C99 их возможности были существенно расширены. Язык D обладает шаблонами более мощными, чем C++.[4]. См. также
Примечания
Литература
Ссылки |
Portal di Ensiklopedia Dunia