Шаблоны классов в C++: практическое руководство
Шаблоны классов в C++: практическое руководство
Узнайте, как использовать шаблоны классов C++ для написания гибкого, типо-независимого кода с наглядными реальными примерами.
Шаблоны — краеугольный камень обобщённого программирования в C++. Они позволяют создавать переиспользуемые компоненты, работающие с любыми типами данных, улучшая читаемость кода и уменьшая дублирование. C++ предлагает два вида шаблонов: функциональные шаблоны и шаблоны классов.
- Функциональные шаблоны
- Шаблоны классов
Шаблоны классов позволяют определить один класс, адаптирующийся к множеству типов данных. Это не только сокращает код, но и облегчает его поддержку.
Объявление шаблона класса
Шаблон класса начинается с ключевого слова template, за которым следует список параметров шаблона в угловых скобках. После этого идёт определение класса.
template <class T>
class MyClass {
private:
T var;
public:
T getVar() const;
};
Здесь T — плейсхолдер для типа данных, который будет указан при инстанциации шаблона. Ключевое слово class можно заменить на typename без изменения семантики.
Инстанциация шаблона класса
После определения шаблона можно создавать объекты, указывая нужный тип:
MyClass<int> intObj;
MyClass<float> floatObj;
MyClass<std::string> stringObj;
Каждая инстанциация генерирует конкретный класс, адаптированный к указанному типу, на этапе компиляции.
Пример 1: Обобщённая обёртка для числа
#include <iostream>
using namespace std;
// Шаблон класса, хранящий одно значение любого типа
template <class T>
class Number {
private:
T value;
public:
Number(T v) : value(v) {}
T getValue() const { return value; }
};
int main() {
Number<int> intNum(7);
Number<double> doubleNum(7.7);
cout << "int Number = " << intNum.getValue() << endl;
cout << "double Number = " << doubleNum.getValue() << endl;
return 0;
}
Вывод
int Number = 7 double Number = 7.7
Обратите внимание: параметр шаблона T используется для переменной-члена, аргумента конструктора и типа возвращаемого значения, позволяя классу работать с любым типом.
Определение членов вне класса
Когда функция-член велика или разделяется между специализациями, её можно определить вне тела класса:
template <class T>
class Sample {
public:
T identity();
};
// Внешнее определение
template <class T>
T Sample<T>::identity() {
return static_cast<T>(1);
}
Обратите внимание на повторение объявления template<class T> перед внешним определением.
Пример 2: Калькулятор с использованием шаблонов классов
#include <iostream>
using namespace std;
template <class T>
class Calculator {
private:
T a, b;
public:
Calculator(T x, T y) : a(x), b(y) {}
T add() const { return a + b; }
T subtract() const { return a - b; }
T multiply() const { return a * b; }
T divide() const { return a / b; }
void display() const {
cout << "a: " << a << ", b: " << b << endl;
cout << "a + b = " << add() << endl;
cout << "a - b = " << subtract() << endl;
cout << "a * b = " << multiply() << endl;
cout << "a / b = " << divide() << endl;
}
};
int main() {
Calculator<int> intCalc(2, 1);
Calculator<float> floatCalc(2.4f, 1.2f);
cout << "Int results:\n";
intCalc.display();
cout << "\nFloat results:\n";
floatCalc.display();
return 0;
}
Вывод
Int results: a: 2, b: 1 a + b = 3 a - b = 1 a * b = 2 a / b = 2 Float results: a: 2.4, b: 1.2 a + b = 3.6 a - b = 1.2 a * b = 2.88 a / b = 2
Шаблоны с несколькими параметрами
Шаблоны классов могут принимать более одного параметра, а также иметь типы по умолчанию:
template <class T, class U, class V = int>
class MultiParam {
T first;
U second;
V third;
};
Пример 3: Многошаблонный класс
#include <iostream>
using namespace std;
template <class T, class U, class V = char>
class ClassTemplate {
private:
T var1;
U var2;
V var3;
public:
ClassTemplate(T v1, U v2, V v3) : var1(v1), var2(v2), var3(v3) {}
void print() const {
cout << "var1 = " << var1 << endl;
cout << "var2 = " << var2 << endl;
cout << "var3 = " << var3 << endl;
}
};
int main() {
ClassTemplate<int, double> obj1(7, 7.7, 'c');
cout << "obj1 values:\n";
obj1.print();
ClassTemplate<double, char, bool> obj2(8.8, 'a', false);
cout << "\nobj2 values:\n";
obj2.print();
return 0;
}
Вывод
obj1 values: var1 = 7 var2 = 7.7 var3 = c obj2 values: var1 = 8.8 var2 = a var3 = 0
В этом примере третий параметр шаблона по умолчанию char, но может быть переопределён, как показано с bool в obj2.
Специализация шаблонов
Иногда нужна специальная реализация для конкретного типа. Для этого используется полная специализация:
// Общий шаблон
template <class T>
class Storage {
T value;
public:
Storage(T v) : value(v) {}
void print() { cout << value; }
};
// Полная специализация для bool
template <>
class Storage<bool> {
bool value;
public:
Storage(bool v) : value(v) {}
void print() { cout << (value ? "true" : "false"); }
};
Лучшие практики
- Используйте
typenameвместоclassдля параметров шаблона — это современный стиль. - Предпочитайте
usingдля псевдонимов шаблонов (C++11):using IntVec = std::vector<int>;. - Избегайте сложной логики в заголовочных файлах — выносите реализацию в .tpp или .inl файлы.
- Используйте
static_assertдля проверки ограничений на типы (C++11). - Для вариативных шаблонов (C++11) используйте
template<class... Args>.
Язык C
- Ключевое слово static в C#: статические переменные, методы и классы
- Вложенные классы в C#: определение, использование и наследование
- Освоение операторов C++: полное руководство с практическими примерами
- Комментарии в C++: лучшие практики для читаемого и поддерживаемого кода
- Классы хранения в C++: область видимости, время жизни и использование
- Полиморфизм в C++: практическое руководство
- Абстракция данных в C++: разделение интерфейса и реализации
- Инкапсуляция данных в C++: защита вашего кода
- Интерфейсы в C++: абстрактные классы и чисто виртуальные функции
- Шаблоны в C++: руководство по обобщённому программированию