Практический курс по ООП C++: https://stepik.org/a/205781
Продолжаем тему
шаблонов классов и давайте представим, что имеется уже знакомый нам шаблон
класса Point:
template <typename T>
class Point {
T x{}, y{};
public:
Point(T a, T b) : x(a), y(b)
{ }
T get_x() { return x; }
T get_y() { return y; }
};
и дополнительно
объявим два обычных класса для представления цветов в разных форматах:
class ColorRGB {
unsigned char r{}, g{}, b{};
};
class ColorUVB {
unsigned char u{}, v{}, b{};
};
А следом объявим
еще один шаблонный класс Rectangle для
представления прямоугольников на плоскости, заданных двумя координатами:
верхнего левого и нижнего правого углов:
template <typename PT, typename CL>
class Rectangle {
Point<PT> sp, ep; // координаты прямоугольника
CL color; // цвет прямоугольника
public:
Rectangle(Point<PT> pt1, Point<PT> pt2) : sp{pt1}, ep{pt2}
{ puts("Rectangle"); }
void set_color(CL cl) { color = cl; }
};
Здесь первый
шаблонный параметр PT определяет тип координат точек прямоугольника, а
второй параметр CL – цвет при отображении прямоугольника.
Теперь мы можем
воспользоваться этим шаблоном следующим образом:
int main()
{
Point<int> start(0, 0), end(10, 20);
Rectangle<int, ColorRGB> rect1(start, end);
return 0;
}
Обратите
внимание, что в качестве второго типа указан класс ColorRGB. Очевидно, так
тоже можно делать, т.к. имя класса (или структуры) – это полноценный
пользовательский тип данных.
Однако при
использовании шаблонов у нас может возникнуть ситуация, когда логика работы
класса меняется для некоторых типов и эту логику следовало бы описывать в
отдельном шаблоне. Например, мы так спроектировали программу, что для класса ColorUVB обработка цвета
в классе Rectangle происходит
несколько иначе, чем в стандартной палитре RGB. Поэтому для
типа ColorUVB было бы
правильно объявить свой шаблон класса Rectangle. Сделать это
можно следующим образом:
template <typename PT>
class Rectangle<PT, ColorUVB> {
Point<PT> sp, ep; // координаты прямоугольника
ColorUVB color; // цвет прямоугольника
public:
Rectangle(Point<PT> pt1, Point<PT> pt2) : sp{pt1}, ep{pt2}
{ puts("Rectangle<PT, ColorUVB>"); }
void set_color(ColorUVB cl) { color = cl; }
};
Теперь, при
создании объекта с типом ColorUVB будет использован именно этот
шаблон класса:
Rectangle<int, ColorUVB> rect2(start, end);
В языке C++ такой подход
называется специализацией шаблонов классов. Шаблон
специализируется под строго определенные типы. В нашем примере под тип ColorUVB, который уже не
нужно прописывать в списке параметров. Причем специализация шаблонов
обязательно должна идти после общего шаблона класса.
Если в шаблоне
заменяются (специализируются) все типы. Например:
template <>
class Rectangle<double, ColorUVB> {
Point<double> sp, ep; // координаты прямоугольника
ColorUVB color; // цвет прямоугольника
public:
Rectangle(Point<double> pt1, Point<double> pt2) : sp{pt1}, ep{pt2}
{ puts("Rectangle<double, ColorUVB>"); }
void set_color(ColorUVB cl) { color = cl; }
};
То это
называется полной специализацией шаблона. В противовес частичной
специализации предыдущего примера.
Соответственно,
использование всех этих шаблонов выглядит следующим образом:
int main()
{
Point<int> start(0, 0), end(10, 20);
Point<double> sd(0, 0), ed(10, 20);
Rectangle<int, ColorRGB> rect1(start, end);
Rectangle<int, ColorUVB> rect2(start, end);
Rectangle<double, ColorUVB> rect3(sd, ed);
return 0;
}
Увидим в консоли
строчки:
Rectangle
Rectangle<PT,
ColorUVB>
Rectangle<double,
ColorUVB>
Вот принцип
специализации шаблонов классов и их назначение.
Наследование шаблонных классов
Давайте теперь
предположим, что у нас есть базовый класс GeomBase, представленный
в виде следующего шаблона:
template <typename T>
class GeomBase {
protected:
T x0{0}, y0{0}, x1{0}, y1{0};
public:
GeomBase(T x0, T y0, T x1, T y1) : x0{x0}, y0{y0}, x1{x1}, y1{y1}
{ }
};
От этого шаблонного
класса наследуется шаблонный класс Rectangle. Записывается
такое наследование следующим образом:
template <typename PT>
class Rectangle : public GeomBase<PT> {
public:
Rectangle(PT x0, PT y0, PT x1, PT y1) : GeomBase<PT>(x0, y0, x1, y1)
{ puts("Rectangle"); }
};
Как видите, все
достаточно очевидно. Главное помнить, что при использовании шаблонного класса
(при наследовании или при инициализации) всегда нужно указывать параметры этого
шаблона в угловых скобках. Во всем остальном наследование работает так же, как
и с обычными классами.
Если же нам
нужно прописать наследование для обычного не шаблонного класса, то это делается
так:
class Rectangle : public GeomBase<int> {
public:
Rectangle(int x0, int y0, int x1, int y1) : GeomBase<int>(x0, y0, x1, y1)
{ puts("Rectangle"); }
};
То есть, вместо
параметра шаблона нужно указать конкретный тип данных, тем самым инстанцировать
шаблон GeomBase и сформировать
на его основе конкретный класс, от которого, затем, и происходит наследование.
Заключение
Это было краткое
введение в шаблоны функций и классов. Но его достаточно для первых шагов в их
применении и понимании текста программ, которые их используют. В качестве
небольшого практического задания предлагаю переписать класс DArray для работы с
динамическим массивом в виде шаблонного класса, в котором тип элементов массива
определялся бы параметром шаблона T. Сам класс DArray, я напомню, мы
ранее создавали в этом курсе.
Практический курс по ООП C++: https://stepik.org/a/205781