Специализация шаблонов
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 66 из 104.
Иногда универсальный шаблон не подходит для всех типов. Например, сравнение строк в стиле C с помощью > сравнивает адреса указателей, а не сам текст. Специализация шаблонов позволяет предоставить пользовательскую реализацию для конкретных типов, сохраняя при этом универсальную версию для всего остального.
Полная специализация заменяет весь шаблон для одного конкретного типа. Вы объявляете её с пустым списком параметров шаблона и указываете конкретный тип:
// Основной шаблон
template <typename T>
class Printer {
public:
void print(T value) {
std::cout << value << std::endl;
}
};
// Полная специализация для const char*
template <>
class Printer<const char*> {
public:
void print(const char* value) {
std::cout << "String: " << value << std::endl;
}
};
Printer<int> intPrinter;
intPrinter.print(42); // Вывод: 42
Printer<const char*> strPrinter;
strPrinter.print("hello"); // Вывод: String: helloШаблоны функций также могут быть специализированы:
template <typename T>
bool isEqual(T a, T b) {
return a == b;
}
template <>
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0;
}
isEqual(5, 5); // Использует универсальную версию
isEqual("hi", "hi"); // Использует специализированную версиюКомпилятор всегда предпочитает наиболее специфичное совпадение — если для используемого конкретного типа существует специализация, она будет выбрана вместо общего шаблона.
Задание
ЛегкоДавайте создадим систему форматирования, учитывающую типы данных, которая продемонстрирует, как специализация шаблонов позволяет настраивать поведение для конкретных типов, сохраняя при этом универсальный вариант для всего остального.
Вы создадите два файла для организации кода вашего шаблона:
Formatter.h: Определите шаблон класса с именемFormatter, который форматирует значения для отображения. Ваш основной шаблон должен работать с любым типом, но вы также создадите специализированные версии для типов, требующих особой обработки.Основной шаблон
Formatterдолжен содержать:- Метод
format(), который принимает значение типа T и выводит:Value: <value>
Создайте полную специализацию для
bool, которая выводит понятный человеку текст вместо 1 или 0:- Метод
format()должен выводить:Boolean: trueилиBoolean: false
Создайте полную специализацию для
const char*, которая добавляет кавычки вокруг строк:- Метод
format()должен выводить:String: "<value>"
Также создайте шаблон функции с именем
formatPair, который принимает два значения одного типа и выводит их вместе в формате:Pair: [<first>, <second>]Создайте специализацию
formatPairдляconst char*, которая заключает каждую строку в кавычки:Pair: ["<first>", "<second>"]- Метод
main.cpp: Считайте четыре входных значения (каждое в отдельной строке):- Целое число (int)
- Число с плавающей точкой (double)
- Логическое значение в виде строки (
"true"или"false") - Строковое значение
Продемонстрируйте работу ваших форматировщиков, создав соответствующие объекты
Formatterи вызвавformat()для каждого типа:- Отформатируйте целое число, используя
Formatter<int> - Отформатируйте число с плавающей точкой, используя
Formatter<double> - Отформатируйте логическое значение, используя
Formatter<bool>(сначала преобразуйте строковый ввод в bool) - Отформатируйте строку, используя
Formatter<const char*>
Затем продемонстрируйте специализацию шаблона функции:
- Вызовите
formatPairс двумя целыми числами: 10 и 20 - Вызовите
formatPairс двумя C-строками:"hello"и"world"
Например, при входных данных 42, 3.14, "true" и "Hello":
Value: 42
Value: 3.14
Boolean: true
String: "Hello"
Pair: [10, 20]
Pair: ["hello", "world"]Обратите внимание, как основной шаблон обрабатывает целые числа и числа с плавающей точкой одинаково, в то время как специализированные версии для bool и const char* обеспечивают настраиваемое форматирование. Компилятор автоматически выбирает наиболее подходящее совпадение для каждого используемого вами типа.
Шпаргалка
Специализация шаблонов позволяет предоставлять пользовательские реализации для конкретных типов, сохраняя при этом общую версию для всех остальных типов.
Синтаксис полной специализации
Объявите специализацию с пустым списком параметров шаблона template <> и укажите конкретный тип:
// Основной шаблон
template <typename T>
class Printer {
public:
void print(T value) {
std::cout << value << std::endl;
}
};
// Полная специализация для const char*
template <>
class Printer<const char*> {
public:
void print(const char* value) {
std::cout << "String: " << value << std::endl;
}
};
Специализация шаблонов функций
Шаблоны функций также могут быть специализированы:
template <typename T>
bool isEqual(T a, T b) {
return a == b;
}
template <>
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0;
}
Выбор компилятора
Компилятор всегда предпочитает наиболее точное совпадение — если для используемого типа существует специализация, она будет выбрана вместо общего шаблона.
Попробуйте сами
#include <iostream>
#include <string>
#include "Formatter.h"
using namespace std;
int main() {
// Чтение входных данных
int intVal;
double doubleVal;
string boolStr;
string strVal;
cin >> intVal;
cin >> doubleVal;
cin >> boolStr;
cin >> strVal;
// Преобразование строки в bool
bool boolVal = (boolStr == "true");
// TODO: Создайте Formatter<int> и отформатируйте целое число
// TODO: Создайте Formatter<double> и отформатируйте число с плавающей запятой
// TODO: Создайте Formatter<bool> и отформатируйте логическое значение
// TODO: Создайте Formatter<const char*> и отформатируйте строку
// Hint: Используйте strVal.c_str(), чтобы получить const char*
// TODO: Вызовите formatPair с двумя целыми числами: 10 и 20
// TODO: Вызовите formatPair с двумя C-строками: "hello" и "world"
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер