Skip to content

Практическая работа №13


Шаблоны C++: Основы обобщенного программирования

Section titled “Шаблоны C++: Основы обобщенного программирования”

Цель работы: Изучить и закрепить принципы обобщенного программирования (Generic Programming) в C++ с использованием шаблонов. Освоить синтаксис и применение функциональных и классовых шаблонов, понять механизм вывода типов, а также научиться использовать специализацию шаблонов для работы с особыми типами данных.

Основные теоретические положения

Section titled “Основные теоретические положения”

1. Что такое Шаблоны (Templates)

Section titled “1. Что такое Шаблоны (Templates)”

Шаблоны — это механизм C++, позволяющий писать код, который может работать с любыми типами данных, не зная их заранее. Это основа обобщенного программирования, которое позволяет создавать универсальные функции и классы, избегая дублирования кода для разных типов (например, max(int, int), max(double, double), max(string, string)).

В C++ существует два основных вида шаблонов:

  • Функциональные шаблоны (Function Templates): Создают семейство функций, которые могут обрабатывать аргументы разных типов.
  • Классовые шаблоны (Class Templates): Создают семейство классов (контейнеров, структур данных), которые могут хранить и обрабатывать данные разных типов.

2. Функциональные Шаблоны

Section titled “2. Функциональные Шаблоны”

Функциональный шаблон определяется с помощью ключевого слова template с последующим списком параметров шаблона в угловых скобках <>. Параметры шаблона могут быть:

  • Параметры-типы (typename T или class T): Представляют любой тип данных.
  • Параметры-константы (Non-type parameters): Представляют константные значения (например, целые числа, используемые для размера массива).
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}

Вывод типов (Type Deduction): Компилятор автоматически определяет тип T на основе типов аргументов, переданных при вызове функции.

Классовые шаблоны используются для создания обобщенных структур данных, таких как списки, векторы, пары или очереди.

template <typename T1, typename T2>
class Pair {
public:
T1 first;
T2 second;
Pair(T1 a, T2 b) : first(a), second(b) {}
};

При создании объекта классового шаблона необходимо явно указать типы: Pair<int, double> p(10, 3.14);.

4. Специализация Шаблонов

Section titled “4. Специализация Шаблонов”

Специализация позволяет предоставить отдельную, оптимизированную или специфическую реализацию для конкретного типа или набора типов.

  • Полная специализация (Full Specialization): Предоставляет реализацию для конкретного типа. Например, для maximum<const char*> (чтобы сравнивать строки, а не адреса).
  • Частичная специализация (Partial Specialization): Применяется только к классовым шаблонам. Предоставляет реализацию для семейства типов, где некоторые параметры шаблона фиксированы или имеют определенную форму (например, для всех указателей T*).
// Частичная специализация классового шаблона Pair для указателей
template <typename T>
class Pair<T*, T*> {
// Специальная реализация для пары указателей
};

Алгоритмы и шаблоны реализации

Section titled “Алгоритмы и шаблоны реализации”

1. Алгоритм создания Функционального Шаблона

Section titled “1. Алгоритм создания Функционального Шаблона”
  1. Определить функцию, которая должна быть обобщенной.
  2. Перед заголовком функции добавить объявление шаблона: template <typename T, ...>.
  3. Заменить конкретные типы данных (например, int, double) на параметр-тип T.
  4. Реализовать логику функции, используя T.

2. Алгоритм создания Классового Шаблона

Section titled “2. Алгоритм создания Классового Шаблона”
  1. Определить класс, который должен быть обобщенным (например, контейнер).
  2. Перед объявлением класса добавить объявление шаблона: template <typename T, ...>.
  3. Заменить типы членов класса и методов на параметр-тип T.
  4. При необходимости, реализовать частичную специализацию для особых случаев (например, для указателей).

Пример 1 — Функциональный Шаблон swap

Section titled “Пример 1 — Функциональный Шаблон swap”
#include <iostream>
#include <string>
// 1. Объявление функционального шаблона
template <typename T>
void swap_values(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int i1 = 5, i2 = 10;
double d1 = 3.14, d2 = 2.71;
std::string s1 = "Hello", s2 = "World";
swap_values(i1, i2); // T = int
swap_values(d1, d2); // T = double
swap_values(s1, s2); // T = std::string
std::cout << "Ints: " << i1 << ", " << i2 << "\n";
std::cout << "Doubles: " << d1 << ", " << d2 << "\n";
std::cout << "Strings: " << s1 << ", " << s2 << "\n";
return 0;
}

Пример 2 — Классовый Шаблон Container

Section titled “Пример 2 — Классовый Шаблон Container”
#include <iostream>
// 2. Объявление классового шаблона
template <typename T>
class Container {
private:
T value;
public:
Container(T val) : value(val) {}
void print() const {
std::cout << "Value: " << value << "\n";
}
T get_value() const {
return value;
}
};
int main() {
Container<int> c_int(42);
Container<std::string> c_str("Template Power");
Container<double> c_double(9.81);
c_int.print();
c_str.print();
c_double.print();
return 0;
}

Пример 3 — Частичная специализация для указателей

Section titled “Пример 3 — Частичная специализация для указателей”
#include <iostream>
#include <string>
// Основной классовый шаблон
template <typename T>
class SmartPrinter {
private:
T data;
public:
SmartPrinter(T d) : data(d) {}
void print() const {
std::cout << "Generic data: " << data << "\n";
}
};
// 3. Частичная специализация для всех указателей (T*)
template <typename T>
class SmartPrinter<T*> {
private:
T* data;
public:
SmartPrinter(T* d) : data(d) {}
void print() const {
if (data) {
std::cout << "Pointer to " << typeid(T).name() << ". Address: " << data << ", Value: " << *data << "\n";
} else {
std::cout << "Null pointer.\n";
}
}
};
int main() {
int x = 100;
int* ptr_x = &x;
SmartPrinter<int> p1(50);
SmartPrinter<int*> p2(ptr_x); // Используется частичная специализация
p1.print();
p2.print();
return 0;
}
  1. Написать программу на языке C++ (стандарт C++11 или выше).
  2. Реализовать:
    • Минимум один функциональный шаблон с явным и неявным выводом типов.
    • Минимум один классовый шаблон с двумя параметрами-типами.
    • Частичную специализацию для классового шаблона (например, для указателей или пар одинаковых типов).
    • Параметр-константу (Non-type parameter) в классовом шаблоне.
  3. Обработка ввода/вывода данных через std::cin/std::cout для пользовательского взаимодействия.
  4. Комментарии в коде, поясняющие ключевые места: template <typename T>, template <typename T1, typename T2>, полная и частичная специализация.

Вывод программы (что должно отображаться)

Section titled “Вывод программы (что должно отображаться)”
  • Демонстрация вызова функционального шаблона с разными типами (int, double, std::string).
  • Демонстрация создания объектов классового шаблона с разными типами.
  • Четкое подтверждение того, что была вызвана специализированная версия шаблона.
  • Демонстрация работы классового шаблона с параметром-константой.

Задания для выполнения

Section titled “Задания для выполнения”
ЗадачаОписание
1Функциональный шаблонСоздайте функциональный шаблон print_info(T value), который выводит значение и имя типа (используя typeid(T).name()). Проверьте его для int, double и char.
2Классовый шаблонРеализуйте классовый шаблон Triple<T1, T2, T3>, который хранит три значения разных типов. Добавьте метод print_all().
3Параметр-константаСоздайте классовый шаблон FixedSizeBuffer<T, N>, где N — это размер буфера (массива) типа T. Реализуйте конструктор и метод get_size().
ЗадачаОписание
4Частичная специализацияДобавьте к классовому шаблону Triple<T1, T2, T3> из Задания 2 частичную специализацию Triple<T, T, T> (когда все три типа одинаковы). В специализированной версии метод print_all() должен выводить дополнительное сообщение: “Все элементы одного типа!“.
5Полная специализацияСоздайте функциональный шаблон compare(T a, T b), который сравнивает два значения. Добавьте полную специализацию для типа const char*, чтобы сравнение выполнялось с помощью strcmp (лексикографически), а не по адресам.
6Шаблон-фабрикаСоздайте функциональный шаблон create_object(Args... args), который использует вариативные шаблоны (variadic templates) и perfect forwarding для создания объекта любого класса T с помощью new T(std::forward<Args>(args)...).
  1. В чём основное преимущество использования шаблонов в C++?
  2. Объясните разницу между функциональным и классовым шаблоном.
  3. Что такое вывод типов (Type Deduction) и когда он не работает?
  4. В чём разница между полной и частичной специализацией шаблонов?
  5. Для чего используются параметры-константы (Non-type template parameters)? Приведите пример.
  6. Почему при работе с шаблонами часто используют ключевое слово typename?
  • Полная реализация базовых заданий (1-3): 50%.
  • Корректная реализация и демонстрация частичной специализации (Задание 4): 20%.
  • Корректная реализация полной специализации (Задание 5): 15%.
  • Чистота кода, комментарии и ответы на контрольные вопросы: 15%.

Рекомендуемая литература

Section titled “Рекомендуемая литература”
  1. Бьерн СтрауструпЯзык программирования C++. (Глава 23: Шаблоны)
  2. Скотт МейерсEffective C++. (Раздел о шаблонах и обобщенном программировании)
  3. Дэвид Вандевурд, Николаи М. ДжосаттисШаблоны C++: Справочник разработчика. (Наиболее полное руководство по шаблонам)
  4. cppreference.com — Разделы “Templates” и “Template specialization” (Онлайн-справочник).
  5. C++ Core Guidelines — Раздел T: Templates and generic programming.