Специализации шаблонов классов A
Прежде чем приступать к рассмотрению специализаций шаблонов классов и причин, по которым в них может возникнуть надобность, добавим в шаблон Queue
функции-члены min() и max(). Они будут обходить все элементы очереди и искать среди них соответственно минимальное и максимальное значения (правильнее, конечно, использовать для этой цели обобщенные алгоритмы min() и max(), представленные в главе 12, но мы определим эти функции как члены шаблона Queue, чтобы познакомиться со специализациями.)
template <class Type> class Queue { // ... public: Type min(); Type max(); // ... }; // найти минимальное значение в очереди Queue template <class Type> Type Queue<Type>::min() { assert( ! is_empty() ); Type min_val = front->item; for ( QueueItem *pq = front->next; pq != 0; pq = pq->next ) if ( pq->item < min_val ) min_val = pq->item; return min_val; } // найти максимальное значение в очереди Queue template <class Type> Type Queue<Type>::max() { assert( ! is_empty() ); Type max_val = front->item; for ( QueueItem *pq = front->next; pq != 0; pq = pq->next ) if ( pq->item > max_val ) max_val = pq->item; return max_val; |
}
Следующая инструкция в функции-члене min()
сравнивает два элемента очереди Queue:
pq->item < min_val
Здесь неявно присутствует требование к типам, которыми может конкретизироваться шаблон класса Queue: такой тип должен либо иметь возможность пользоваться предопределенным оператором “меньше” для встроенных типов, либо быть классом, в котором определен оператор operator<(). Если же этого оператора нет, то попытка применить min() к очереди приведет к ошибке компиляции в том месте, где вызывается несуществующий оператор сравнения. (Аналогичная проблема существует и в max(), только касается оператора operator>()).
Предположим, что шаблон класса Queue
нужно конкретизировать таким типом:
class LongSouble { public: LongDouble( double dbval ) : value( dval ) { } bool compareLess( const LongDouble & ); private: double value; |
Но в этом классе нет оператора operator<(), позволяющего сравнивать два значения типа LongDouble, поэтому использовать для очереди типа Queue<LongDouble>
функции-члены min() и max()
нельзя. Одним из решений этой проблемы может стать определение глобальных operator<() и operator>(), в которых для сравнения значений типа Queue<LongDouble>
используется функция-член compareLess. Эти глобальные операторы вызывались бы из min() и max()
автоматически при сравнении объектов из очереди.
Однако мы рассмотрим другое решение, связанное со специализацией шаблонов класса: вместо общих определений функций-членов min() и max() при конкретизации шаблона Queue
типом LongDouble мы определим специальные экземпляры Queue<LongDouble>::min() и Queue<LongDouble>::max(), основанные на функции-члене compareLess() класса LongDouble.
Это можно сделать, если воспользоваться явным определением специализации, где после ключевого слова template
идет пара угловых скобок <>, а за ней – определение специализации члена класса. В приведенном примере для функций-членов min() и max()
класса Queue<LongDouble>, конкретизированного из шаблона, определены явные специализации:
// определения явных специализаций template<> LongDouble Queue<LongDouble>::min() { assert( ! is_empty() ); LongDouble min_val = front->item; for ( QueueItem *pq = front->next; pq != 0; pq = pq->next ) if ( pq->item.compareLess( min_val ) ) min_val = pq->item; return min_val; } template<> LongDouble Queue<LongDouble>::max() { assert( ! is_empty() ); LongDouble max_val = front->item; for ( QueueItem *pq = front->next; pq != 0; pq = pq->next ) if ( max_val.compareLess( pq->item ) ) max_val = pq->item; return max_val; |
}
Хотя тип класса Queue<LongDouble>
конкретизируется по шаблону, в каждом объекте этого типа используются специализированные функции-члены min() и max() – не те, что конкретизируются по обобщенным определениям этих функций в шаблоне класса Queue.
Поскольку определения явных специализаций min() и max() – это определения невстроенных функций, помещать их в заголовочный файл нельзя: они обязаны находится в файле с текстом программы. Однако явную специализацию функции можно объявить, не определяя. Например:
// объявления явных специализаций функций-членов template <> LongDouble Queue<LongDouble>::min(); |
Поместив эти объявления в заголовочный файл, а соответствующие определения – в исходный, мы можем организовать код так же, как и для определений функций-членов обычного класса.
Иногда определение всего шаблона оказывается непригодным для конкретизации некоторым типом. В таком случае программист может специализировать шаблон класса целиком. Напишем полное определение класса Queue<LongDouble>:
// QueueLD.h: определяет специализацию класса Queue<LongDouble> #include "Queue.h" template<> Queue<LongDouble> { Queue<LongDouble>(); ~Queue<LongDouble>(); LongDouble& remove(); void add( const LongDouble & ); bool is_empty() const; LongDouble min(); LongDouble max(); private: // Некоторая реализация |
Явную специализацию шаблона класса можно определять только после того, как общий шаблон уже был объявлен (хотя и не обязательно определен). Иными словами, должно быть известно, что специализируемое имя обозначает шаблон класса. Если в приведенном примере не включить заголовочный файл Queue.h перед определением явной специализации шаблона, компилятор выдаст сообщение об ошибке, указывая, что Queue – это не имя шаблона.
Если мы определяем специализацию всего шаблона класса, то должны определить также все без исключения функции-члены и статические данные-члены. Определения членов из общего шаблона никогда не используются для создания определений членов явной специализации: множества членов этих шаблонов могут различаться. Чтобы предоставить определение явной специализации для типа класса Queue<LongDouble>, придется определить не только функции-члены min() и max(), но и все остальные.
Если класс специализируется целиком, лексемы template<>
помещаются только перед определением явной специализации всего шаблона:
#include "QueueLD.h" // определяет функцию-член min() // из специализированного шаблона класса |
Класс не может в одних файлах конкретизироваться из общего определения шаблона, а в других – из специализированного, если задано одно и то же множество аргументов. Например, специализацию шаблона QueueItem<LongDouble>
необходимо объявлять в каждом файле, где она используется:
// ---- File1.C ---- #include "Queue.h" void ReadIn( Queue<LongDouble> *pq ) { // использование pq->add() // приводит к конкретизации QueueItem<LongDouble> } |
// ---- File2.C ---- #include "QueueLD.h" void ReadIn( Queue<LongDouble> * ); int main() { // используется определение специализации для Queue<LongDouble> Queue<LongDouble> *qld = new Queue<LongDouble>; ReadIn( qld ); // ... |
Эта программа некорректна, хотя большинство компиляторов ошибку не обнаружат: заголовочный файл QueueLD.h
следует включать во все файлы, где используется Queue<LongDouble>, причем до первого использования.