Шаблоны классов
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 65 из 104.
Точно так же, как шаблоны функций позволяют писать функции, не зависящие от типа, шаблоны классов позволяют создавать классы, не зависящие от типа. Именно так работают контейнеры вроде std::vector — вы определяете структуру один раз, а компилятор генерирует конкретные версии для каждого используемого типа.
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
std::cout << intBox.getValue() << std::endl; // 42
std::cout << strBox.getValue() << std::endl; // Hello
}В отличие от шаблонов функций, при создании объектов из шаблонов классов вы должны явно указывать тип, используя угловые скобки. Затем компилятор генерирует полное определение класса для этого конкретного типа.
Шаблоны классов могут иметь несколько параметров типа и даже нетиповые параметры, такие как целые числа:
template <typename T, int Size>
class FixedArray {
T data[Size];
public:
T& operator[](int index) { return data[index]; }
int size() const { return Size; }
};
FixedArray<double, 5> arr; // Массив из 5 чисел типа double
arr[0] = 3.14;При определении функций-членов вне класса вы должны повторить объявление шаблона:
template <typename T>
class Container {
T* data;
public:
Container();
~Container();
};
template <typename T>
Container<T>::Container() : data(nullptr) {}
template <typename T>
Container<T>::~Container() { delete data; }Шаблоны классов составляют основу обобщенного программирования в C++, позволяя создавать переиспользуемые структуры данных, работающие с любым типом.
Задание
ЛегкоДавайте создадим универсальную систему хранения, используя шаблоны классов, чтобы создать гибкий контейнер, способный хранить данные любого типа. Вы организуете свой шаблонный класс в заголовочном файле и продемонстрируете его универсальность с различными типами в основной программе.
Вы создадите два файла:
Storage.h: Определите шаблон класса с именемStorage, который выступает в качестве простого контейнера для одного значения с некоторыми полезными операциями:Ваш шаблон
Storageдолжен иметь:- Закрытый член (private member) для хранения значения
- Конструктор, принимающий начальное значение
- Метод
getValue(), который возвращает сохраненное значение - Метод
setValue(), который обновляет сохраненное значение - Метод
isEmpty(), который возвращаетtrue, если значение равно значению типа T, созданному по умолчанию, иfalseв противном случае
Также создайте шаблон класса с именем
Pairс двумя параметрами типа, который хранит два связанных значения:- Закрытые члены для первого и второго значений (потенциально разных типов)
- Конструктор, инициализирующий оба значения
- Методы
getFirst()иgetSecond()для получения каждого значения - Метод
display(), который выводит:(<first>, <second>)
main.cpp: Считайте четыре входных значения (каждое на отдельной строке):- Целое число
- Новое целое число
- Строковое значение
- Число с плавающей точкой (double)
Продемонстрируйте работу ваших шаблонов следующим образом:
- Создайте
Storage<int>с первым целым числом, выведя:Int storage: <value> - Обновите его вторым целым числом с помощью
setValue(), затем выведите:Updated: <value> - Создайте
Storage<std::string>со строковым вводом, выведя:String storage: <value> - Проверьте, пусто ли строковое хранилище, и выведите:
Is empty: <true/false>(выведитеtrueилиfalse) - Создайте
Pair<std::string, double>со строковым и дробным значениями, затем вызовитеdisplay() - Создайте
Pair<int, int>с обоими целочисленными входами, затем вызовитеdisplay()
Например, при входных данных 42, 100, Hello и 3.14:
Int storage: 42
Updated: 100
String storage: Hello
Is empty: false
(Hello, 3.14)
(42, 100)Обратите внимание, как один и тот же шаблон Storage беспрепятственно работает с целыми числами и строками, и как Pair может объединять различные типы. Компилятор генерирует отдельные определения классов для каждой используемой вами комбинации типов. Не забудьте использовать синтаксис template <typename T> для шаблонов с одним типом и template <typename T, typename U> для шаблонов с несколькими параметрами типа.
Шпаргалка
Шаблоны классов позволяют создавать независимые от типа классы. Компилятор генерирует конкретные версии для каждого используемого типа.
Базовый шаблон класса
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
};При создании объектов на основе шаблонов классов необходимо явно указывать тип, используя угловые скобки:
Box<int> intBox(42);
Box<std::string> strBox("Hello");Несколько параметров типа
Шаблоны классов могут иметь несколько параметров типа:
template <typename T, typename U>
class Pair {
T first;
U second;
public:
Pair(T f, U s) : first(f), second(s) {}
};Нетиповые параметры
Шаблоны также могут принимать нетиповые параметры, такие как целые числа:
template <typename T, int Size>
class FixedArray {
T data[Size];
public:
int size() const { return Size; }
};
FixedArray<double, 5> arr; // Массив из 5 чисел типа doubleОпределение функций-членов вне класса
При определении функций-членов вне класса необходимо повторить объявление шаблона:
template <typename T>
class Container {
T* data;
public:
Container();
~Container();
};
template <typename T>
Container<T>::Container() : data(nullptr) {}
template <typename T>
Container<T>::~Container() { delete data; }Попробуйте сами
#include <iostream>
#include <string>
#include "Storage.h"
using namespace std;
int main() {
// Чтение входных данных
int intValue1;
int intValue2;
string strValue;
double doubleValue;
cin >> intValue1;
cin >> intValue2;
cin >> strValue;
cin >> doubleValue;
// TODO: Создайте Storage<int> с первым целым числом и выведите: Int storage: <value>
// TODO: Обновите значение вторым целым числом с помощью setValue(), выведите: Updated: <value>
// TODO: Создайте Storage<std::string> со строковым вводом, выведите: String storage: <value>
// TODO: Проверьте, пуст ли строковый накопитель, выведите: Is empty: true/false
// TODO: Создайте Pair<std::string, double> со строкой и числом double, вызовите display()
// TODO: Создайте Pair<int, int> с обоими целыми числами, вызовите display()
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Итоги — Обобщенный контейнер