C++ для начинающих.

16.9. Специализации шаблонов классов A.

Прежде чем приступать к рассмотрению специализаций шаблонов классов и причин, по которым в них может возникнуть надобность, добавим в шаблон Queue функции-члены min() и max(). Они будут обходить все элементы очереди и искать среди них соответственно минимальное и максимальное значения (правильнее, конечно, использовать для этой цели обобщенные алгоритмы min() и max(), представленные в главе 12, но мы определим эти функции как члены шаблона Queue, чтобы познакомиться со специализациями.).

Template class Type.

Class Queue {

// ...

Public:

Type min();

Type max();

// ...

};

// найти минимальное значение в очереди Queue.

Template class Type.

Type QueueType::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 QueueType::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()).

Предположим, что шаблон класса Queue нужно конкретизировать таким типом:

Class LongSouble {

Public:

LongDouble( double dbval ) : value( dval ) { }.

Bool compareLess( const LongDouble & );

Private:

Double value;

};

Но в этом классе нет оператора operator(), позволяющего сравнивать два значения типа LongDouble, поэтому использовать для очереди типа Queue функции-члены min() и max() нельзя. Одним из решений этой проблемы может стать определение глобальных operator(), в которых для сравнения значений типа Queue используется функция-член compareLess. Эти глобальные операторы вызывались бы из min() и max() автоматически при сравнении объектов из очереди.

Однако мы рассмотрим другое решение, связанное со специализацией шаблонов класса: вместо общих определений функций-членов min() и max() при конкретизации шаблона Queue типом LongDouble мы определим специальные экземпляры Queue::min() и Queue::max(), основанные на функции-члене compareLess() класса LongDouble.

Это можно сделать, если воспользоваться явным определением специализации, где после ключевого слова template идет пара угловых скобок , а за ней - определение специализации члена класса. В приведенном примере для функций-членов min() и max() класса Queue, конкретизированного из шаблона, определены явные специализации:

// определения явных специализаций.

Template LongDouble Queue::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 QueueLongDouble ::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 конкретизируется по шаблону, в каждом объекте этого типа используются специализированные функции-члены min() и max() - не те, что конкретизируются по обобщенным определениям этих функций в шаблоне класса Queue.

Поскольку определения явных специализаций min() и max() - это определения невстроенных функций, помещать их в заголовочный файл нельзя: они обязаны находится в файле с текстом программы. Однако явную специализацию функции можно объявить, не определяя. Например:

// объявления явных специализаций функций-членов.

Template LongDouble Queue LongDouble ::min();

Template LongDouble QueueLongDouble ::max();

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

Иногда определение всего шаблона оказывается непригодным для конкретизации некоторым типом. В таком случае программист может специализировать шаблон класса целиком. Напишем полное определение класса Queue:

// QueueLD.h: определяет специализацию класса QueueLongDouble.

#include "Queue.h".

Template QueueLongDouble {

QueueLongDouble ();

~QueueLongDouble ();

LongDouble& remove();

Void add( const LongDouble & );

Bool is_empty() const;

LongDouble min();

LongDouble max();

Private:

// Некоторая реализация.

};

Явную специализацию шаблона класса можно определять только после того, как общий шаблон уже был объявлен (хотя и не обязательно определен). Иными словами, должно быть известно, что специализируемое имя обозначает шаблон класса. Если в приведенном примере не включить заголовочный файл Queue.h перед определением явной специализации шаблона, компилятор выдаст сообщение об ошибке, указывая, что Queue - это не имя шаблона.

Если мы определяем специализацию всего шаблона класса, то должны определить также все без исключения функции-члены и статические данные-члены. Определения членов из общего шаблона никогда не используются для создания определений членов явной специализации: множества членов этих шаблонов могут различаться. Чтобы предоставить определение явной специализации для типа класса Queue, придется определить не только функции-члены min() и max(), но и все остальные.

Если класс специализируется целиком, лексемы template помещаются только перед определением явной специализации всего шаблона:

#include "QueueLD.h".

// определяет функцию-член min().

// из специализированного шаблона класса.

LongDouble QueueLongDouble::min() { }.

Класс не может в одних файлах конкретизироваться из общего определения шаблона, а в других - из специализированного, если задано одно и то же множество аргументов. Например, специализацию шаблона QueueItem необходимо объявлять в каждом файле, где она используется:

// ---- File1.C ----

#include "Queue.h".

Void ReadIn( QueueLongDouble *pq ) {

// использование pq-add().

// приводит к конкретизации QueueItemLongDouble.

}

// ---- File2.C ----

#include "QueueLD.h".

Void ReadIn( QueueLongDouble * )ошибку не обнаружат: заголовочный файл QueueLD.h.

Следует включать во все файлы, где используется Queue,

Причем до первого использования.