Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
При специализации шаблона функции следует предоставить аргументы для каждого параметра первоначального шаблона. Для указания специализации шаблона используется ключевое слово template, сопровождаемое парой пустых угловых скобок (<>). Пустые скобки означают, что аргументы будут предоставлены для всех параметров первоначального шаблона:
// специальная версия compare() для работы с указателями на символьные
// массивы
template <>
int compare(const char* const &p1, const char* const &p2) {
return strcmp(p1, p2);
}
Трудная для понимания часть этой специализации относится к типам параметра функции. При определении специализации типы параметров функции должны совпадать с соответствующими типами ранее объявленного шаблона:
template <typename Т> int compare(const T&, const T&);
В этой специализации параметры функции являются ссылками на константные типы. Подобно псевдонимам типа, взаимодействие между типами параметра шаблона, указателями и константами может удивить (см. раздел 2.5.1).
Необходимо определить специализацию шаблона этой функции с типом const char* для параметра Т. Функция потребует ссылки на константную версию этого типа. Константная версия типа указателя — это константный указатель, а не указатель на константу (см. раздел 2.4.2). В данной специализации следует использовать тип const char* const &, являющийся ссылкой на константный указатель на константный символ.
Перегрузка функций или специализация шаблонаПри определении специализации шаблона функции разработчик, по существу, выполняет задачу компилятора. Таким образом, определение предоставляется для использования специфического экземпляра первоначального шаблона. Важно понимать, что специализация — это создание экземпляра функции; а не перегрузка ее экземпляра.
Специализация создает экземпляр шаблона, а не перегружает его. В результате специализация не затрагивает механизм подбора функций.
Может ли определение некой функции как специализации шаблона или как независимой, не шаблонной функции повлиять на подбор функций? Предположим, например, что имеется определение двух версий шаблонной функции compare(): той, что получает параметры как ссылки на массив, и другой, которая получает тип const T&. Факт наличия специализации для символьных указателей никак не влияет на подбор функции:
compare("hi", "mom")
Когда функция compare() вызывается для строкового литерала, оба шаблона функции оказываются подходящими и обеспечивают одинаково хорошее (т.е. точное) соответствие вызову. Однако версия с параметрами символьного массива более специализирована (см. раздел 16.3), она и выбирается для этого вызова.
Если бы была определена версия функции compare(), получающая указатели на символы, как простая, не шаблонная функция (а не как специализация шаблона), то этот вызов разрешится по-другому. В данном случае было бы три подходящих функции: эти два шаблона и не шаблонная версия указателя на символ. Все три одинаково хорошо подходят для этого вызова. Как уже упоминалось, когда нешаблонная функция обеспечивает одинаково хорошее соответствие с шаблонной, выбирается нешаблонная функция (см. раздел 16.3).
Ключевая концепция. Обычные правила области видимости относятся и к специализацииЧтобы специализировать шаблон, объявление его оригинала должно быть в области видимости. Кроме того, объявление специализации должно быть в области видимости перед любым кодом, использующим экземпляр шаблона.
Пропуск объявления обычных классов и функций найти очень просто — компилятор не сможет обработать такой код. Но при отсутствии объявления специализации компилятор обычно создает код, используя первоначальный шаблон. Поэтому ошибки в порядке объявления шаблона и его специализации довольно просто допустить, но очень трудно найти.
Использование специализации и экземпляра первоначального шаблона с тем же набором аргументов шаблона является ошибкой. Но компилятор вряд ли обнаружит эту ошибку.
Шаблоны и их специализации должны быть объявлены в том же файле заголовка. Объявления всех шаблонов с данным именем должны располагаться сначала, а затем все специализации этих шаблонов.
Специализация шаблона классаКроме специализации шаблонов функций, вполне можно также специализировать шаблоны классов. В качестве примера определим специализацию библиотечного шаблона hash, который можно использовать для хранения объектов класса Sales_data в неупорядоченном контейнере. По умолчанию неупорядоченные контейнеры используют для организации своих элементов класс hash<key_type> (см. раздел 11.4). Чтобы использовать его с собственным типом данных, следует определить специализацию шаблона hash. Специализированный класс hash должен определять следующее.
• Перегруженный оператор вызова (см. раздел 14.8), возвращающий тип size_t и получающий объект типа ключа контейнера.
• Два члена-типа result_type и argument_type, соответствующие типу возвращаемого значения и типу аргумента оператора вызова.
• Стандартный конструктор и оператор присвоения копии, которые могут быть определены неявно (см. раздел 13.1.2).
Единственное осложнение в определении этой специализации класса hash состоит в том, что специализация шаблона должна быть в том же пространстве имен, в котором определяется первоначальный шаблон. Более подробная информация о пространствах имен приведена в разделе 18.2, а пока достаточно знать, что к пространству имен можно добавлять члены. Для этого следует сначала открыть пространство имен:
// открыть пространство имен std, чтобы можно было специализировать
// класс std::hash
namespace std {
} // закрыть пространство имен std; обратите внимание: никакой точки с
// запятой после закрывающей фигурной скобки
Любые определения, расположенные между открывающей и закрывающей фигурными скобками, будут частью пространства имен std.
Следующий код определяет специализацию класса hash для класса Sales_data:
// открыть пространство имен std, чтобы можно было специализировать
// класс std::hash
namespace std {
template <> // определение специализации с параметром
struct hash<Sales_data> // шаблона класса Sales_data
{
// тип, используемый для неупорядоченного контейнера hash, должен
// определять следующие типы
typedef size_t result_type;
typedef Sales_data argument_type; // по умолчанию этому типу
// требуется оператор ==
size_t operator()(const Sales_data& s) const;
// класс использует синтезируемые функции управления копированием
// и стандартный конструктор
};
size_t
hash<Sales_data>::operator()(const Sales_data& s) const {
return hash<string>()(s.bookNo) ^
hash<unsigned>()(s.units_sold) ^
hash<double>()(s.revenue);
}
} // закрыть пространство имен std; обратите внимание: никакой точки с
// запятой после закрывающей фигурной скобки
Определение hash<Sales_data> начинается с части template<>, означающей, что определяется полностью специализированный шаблон. Специализируемый шаблон называется hash, а специализированная версия — hash<Sales_data>. Члены класса следуют непосредственно из требований для специализации шаблона hash.
Подобно любым другим классам, специализируемые члены можно определить в классе или вне его, как это сделано здесь. Перегруженный оператор вызова должен определять хеш-функцию по значениям заданного типа. Эта функция обязана возвращать каждый раз тот же результат, когда она вызывается для данного значения. Хеш-функция практически всегда возвращает другой результат для не равных объектов.
Все сложности определения хорошей хеш-функции делегируем библиотеке. Библиотека определяет специализации класса hash для встроенных типов и для большинства библиотечных типов. Безымянный объект hash<string> используется для создания хеш-кода для переменной-члена bookNo, объект типа hash<unsigned> для создания хеш-кода из переменной-члена units_sold и объекта типа hash<double> для создания хеш-кода из переменной-члена revenue. Применение к этим результатам оператора исключающего ИЛИ (см. раздел 4.8) сформирует общий хеш-код для заданного объекта класса Sales_data.