Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

Здесь, как и при использовании шаблонов функций (см. раздел 10.5.3), необходимо применить опцию компилятора, подавляющую неявные конкретизации. Эта опция вынуждает компилятор предполагать, что все конкретизации шаблонов будут объявляться явно.

Упражнение 16.9

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

Упражнение 16.10

Имеется шаблон класса Screen, разработанный в упражнениях из предыдущих разделов (в том числе функции-члены, определенные в упражнении 16.5 из раздела 16.3, и статические члены, определенные в упражнении 16.7 из раздела 16.5). Организуйте программу так, чтобы воспользоваться преимуществами модели компиляции с разделением.

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 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>()).

class LongSouble { public:

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

bool compareLess( const LongDouble & ); private:

double value;

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

};

Но в этом классе нет оператора 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();

template <> LongDouble Queue<LongDouble>::max();

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

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

// 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<LongDouble>:

};

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

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

Если класс специализируется целиком, лексемы template<> помещаются только перед

#include "QueueLD.h"

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

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

класса

определением явной специализации всего шаблона:

LongDouble Queue<LongDouble>::min() { }