Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Следует заметить, что хеш-функция определена для хеширования всех трех переменных-членов, чтобы она была совместима с определением оператора operator== класса Sales_data (см. раздел 14.3.1). По умолчанию неупорядоченные контейнеры используют специализацию хеша, соответствующую типу key_type, наряду с оператором равенства типа ключа.
С учетом того, что специализация находится в области видимости, она будет использоваться автоматически при использовании класса Sales_data как ключ в одном из этих контейнеров:
// использует hash<Sales_data> и оператор operator== класса Sales_data
// из раздела 14.3.1
unordered_multiset<Sales_data> SDset;
Поскольку hash<Sales_data> использует закрытые члены класса Sales_data, этот класс следует сделать другом класса Sales_data:
template <class T> class std::hash; // нужно для объявления
// дружественным
class Sales_data {
friend class std::hash<Sales_data>;
// другие члены, как прежде
};
Здесь указано, что специфический экземпляр hash<Sales_data> является дружественным. Поскольку данный экземпляр определяется в пространстве имен std, следует помнить, что этот тип хеша определяется в пространстве имен std. Следовательно, объявление friend относится к std::hash.
Чтобы позволить пользователям класса Sales_data использовать специализацию шаблона hash, следует определить эту специализацию в заголовке Sales_data.
Частичная специализация шаблона классаВ отличие от шаблона функции, специализация шаблона класса не обязана предоставлять аргументы для каждого параметра шаблона. Можно определить некоторые из них, но не все.
Частичная специализация (partial specialization) шаблона класса сама является шаблоном. Пользователи должны предоставить аргументы для тех параметров шаблона, которые не затронуты специализацией.
Частично можно специализировать только шаблон класса. Нельзя частично специализировать шаблон функции.
Библиотечный тип remove_reference был представлен в разделе 16.2.3, он работает с серией специализаций:
// первоначальный, наиболее общий шаблон
template <class Т> struct remove_reference {
typedef T type;
};
// частичные специализации, которые будут использоваться для ссылок
// на l- и r-значения
template <class Т> struct remove_reference<T&> // ссылки на l-значение
{ typedef Т type; };
template <class T> struct remove_reference<T&&> // ссылки на r-значение
{ typedef T type; };
Первый шаблон определяет самую общую версию. Его экземпляр может быть создан с любым типом; он использует свой аргумент шаблона как тип для своего члена type. Следующие два класса — это частичные специализации первоначального шаблона.
Поскольку частичная специализация — это шаблон, начнем, как обычно, с определения параметров шаблона. Подобно любой другой специализации, у частичной специализации то же имя, что и у специализируемого шаблона. Список параметров специализации шаблона включает элементы для каждого параметра шаблона, тип которого не был определен полностью при частичной специализации. После имени класса располагаются аргументы для параметров специализируемого шаблона. Эти аргументы располагаются в угловых скобках после имени шаблона. Аргументы позиционально соответствуют параметрам первоначального шаблона.
Список параметров шаблона частичной специализации — это подмножество или специализация списка параметров первоначального шаблона. В данном случае у специализаций то же количество параметров, что и у первоначального шаблона. Но тип параметров в специализациях отличается от первоначального шаблона. Специализация будут использоваться для ссылок на типы l- и r-значений соответственно:
int i;
// decltype(42) - это int, используется первоначальный шаблон
remove_reference<decltype(42)>::type a;
// decltype(i) - это int&, используется первая (Т&) частичная
// специализация
remove_reference<decltype(i)>::type b;
// decltype(std::move(i)) - это int&&, используется вторая (т.е., T&&)
// частичная специализация
remove_reference<decltype(std::move(i))>::type c;
У всех трех переменных, a, b и с, тип int.
Специализация членов, но не классаВместо специализации всего шаблона можно специализировать только одну или несколько его функций-членов. Например, если Foo — это шаблон класса с членом Bar, можно специализировать только этот член:
template <typename Т> struct Foo {
Foo (const T &t = T()): mem(t) { }
void Bar() { /* ... */ }
T mem;
// другие члены класса Foo
};
template<> // специализация шаблона
void Foo<int>::Bar() // специализация члена Bar класса Foo<int>
{
// осуществить всю специализированную обработку, относящуюся к целым
// числам
}
Здесь специализируется только один член класса Foo<int>. Другие его члены предоставляются шаблоном Foo:
Foo<string> fs; // создает экземпляр Foo<string>::Foo()
fs.Bar(); // создает экземпляр Foo<string>::Bar()
Foo<int> fi; // создает экземпляр Foo<int>::Foo()
fi.Bar(); // использует специализацию Foo<int>::Bar()
При использовании шаблона Foo с любым типом, кроме int, члены экземпляра создаются, как обычно. При использовании шаблона Foo с типом int все члены экземпляра, кроме Bar, создаются, как обычно. Если использовать член Bar класса Foo<int>, то получится специализированное определение.
Упражнения раздела 16.5Упражнение 16.62. Определите собственную версию класса hash<Sales_data> и контейнер unordered_multiset объектов класса Sales_data. Поместите в контейнер несколько транзакций и выведите его содержимое.
Упражнение 16.63. Определите шаблон функции для подсчета количества вхождений заданного значения в векторе. Проверьте программу, передав ей вектор значений типа double, вектор целых чисел и вектор строк.
Упражнение 16.64. Напишите специализированную версию шаблона из предыдущего упражнения для обработки вектора vector<const char*> и используйте ее в программе.
Упражнение 16.65. В разделе 16.3 были определены две перегруженных версии функции debug_rep(), одна из которых получает параметр типа const char*, а вторая — типа char*. Перепишите эти функции как специализации.
Упражнение 16.66. Каковы преимущества и недостатки перегрузки функций debug_rep() по сравнению с определением специализаций?
Упражнение 16.67. Повлияет ли определение этих специализаций на подбор функций debug_rep()? Почему?
Резюме
Шаблоны — это отличительная особенность языка С++ и основа его стандартной библиотеки. Шаблон представляет собой независимый от типа "чертеж", используемый компилятором для создания конкретных экземпляров указанных классов или функций. Шаблон разрабатывается один раз, а его экземпляры компилятор создает для соответствующего типа или значения по мере его применения.
Можно определять шаблоны функций и классов. Библиотечные алгоритмы являются шаблонами функций, а библиотечные контейнеры — шаблонами классов.
Явный аргумент шаблона позволяет фиксировать тип или значение одного или нескольких параметров шаблона. К параметрам с явным аргументом шаблона применимы нормальные преобразования.
Специализация шаблона — это отдельное специальное определение, позволяющее создать такую версию шаблона, в которой для одного или нескольких параметров указан определенный тип или значение. Специализация полезна в случае, когда для некоторых типов стандартное определение шаблона неприменимо.
Главная часть последнего выпуска стандарта языка С++ относится к шаблонам с переменным количеством аргументов. Такой шаблон способен получать переменное количество аргументов разных типов. Шаблоны с переменным количеством аргументов позволяют написать такие функции, как функция-член emplace() классов контейнеров и библиотечная функция make_shared(), передающая аргументы конструктору объекта.
Термины