C++. Шаблоны классов. Полная специализация. Частичная специализация

Шаблоны классов. Полная специализация. Частичная специализация

Перед изучением данной темы рекомендуется ознакомиться со следующей темой:

Содержание


Поиск на других ресурсах:

1. Концепция полной и частичной специализации. Общая форма
В самом простом случае, объявление шаблонного класса, оперирующего обобщенным типом T, выглядит следующим образом:
template <class T>
class ClassName
{
  // тело класса
  // ...
}
здесь
  • ClassName – имя шаблонного класса;
  • T – обобщенное имя типа, которое может быть использовано в классе ClassName. Это имя может быть и другим.
На основе вышеуказанного шаблона можно объявлять конкретные экземпляры класса, непосредственно привязанные к некоторому типу.
ClassName<int> objInt;    // формируется класс для типа int
ClassName<double> objDouble;  // формируется класс для типа double
Экземпляры класса objInt и objDouble формируются на этапе компиляции. Такой способ создания кода является частным случаем статического полиморфизма.
В языке C++ допускается объявлять так называемую «специализацию» шаблонного класса. Специализация позволяет отредактировать создание экземпляра обобщенного класса для особых случаев. Такими особыми случаями могут быть:
  • запрет использования класса для некоторых типов. Например, если класс оперирует числами, то строковые типы здесь неуместны (невозможно выполнять математические вычисления над символами);
  • предусмотрена реализация для указателя на обобщенный тип. Например, для обобщенного типа T нужно реализовать объявление указателя на этот тип (T*) с учетом того, что тип char* может быть строкой символов и обрабатывается по особому;
  • другие случаи.
Специализация шаблонного класса может быть двух видов:
  • полная специализация;
  • частичная специализация.
Полная специализация имеет следующую общую форму:
template <>
class ClassName<type>
{
  // тело класса
  // ...
}
здесь
  • ClassName – имя класса, для которого объявляется полная специализация. Важно: перед объявлением полной специализации класс должен быть объявлен как шаблонный;
  • type – тип, определяющий специализацию шаблонного типа, объявленного в шаблоне класса ClassName. Тип type может быть любым стандартным типом (int, float, double и т.д.) или типом, определенным в программе.
Учитывая вышеприведенную общую форму, объявление экземпляра класса при полной специализации может быть таким
ClassName<type> obj;
здесь
  • obj – имя экземпляра (объекта) класса;
  • type – тип, определяемый при объявлении полной специализации класса.
Частичная специализация имеет следующую общую форму:
template <class T>
class ClassName<specialization_T>
{
  // тело класса
  // ...
}
здесь
  • ClassName – имя шаблонного класса, для которого определяется частичная специализация. Важно: шаблон класса ClassName должен быть обязательно объявлен в программе;
  • T – тип класса, который определен в шаблоне;
  • specialization_T – специализация типа T. Это может быть, например, указатель типа T, который определен как T*.
Учитывая вышеприведенную общую форму частичной специализации, объявление объекта класса может быть следующим:
ClassName<specialization_T> obj;
здесь
  • obj – имя объекта класса;
  • specialization_T – специализация типа T (например T*, T** и т.д.);
  • ClassName – имя шаблонного класса.

 

2. Пример реализации полной и частичной специализации. Класс Value<T>
В примере демонстрируются:
  • объявление шаблонного класса Value<T>;
  • полная специализация класса Value<T> для типа char*;
  • полная специализация класса Value<T> для типа void*;
  • частичная специализация класса Value<T> для типа T* — указатель на любой тип кроме char*.
#include <iostream>
using namespace std;

// Тема: Шаблонные классы. Полная специализация. Частичная специализация.

// 1. Обычное объявление шаблонного класса
// Задан шаблонный класс Value<T>,
// сохраняющий некоторое значение.
template <class T>
class Value
{
private:
  T value;

public:
  // Конструктор с 1 параметром
  Value(T value = 0) : value(value) { }

  // Метод тестирования
  void Print(string msg)
  {
    cout << msg << " = " << value << endl;
  }

  // Метод, возвращающий значение
  T Get() { return value; }
};

// 2. Полная специализация для типа char*
// Полная специализация для типа char*,
// признаком полной специализации является строка template<>
template <>
class Value<char*>
{
private:
  char* s;

  // Копирование s <= _s
  void Copy(const char* _s)
  {
    int len = 0;
    while (_s[len++] != '\0');
    s = new char[len + 1];
    for (int i = 0; i <= len; i++)
    s[i] = _s[i];
  }

public:
  // Конструктор
  Value(const char* _s = nullptr)
  {
    // Копирование s <= _s
    if (_s != nullptr)
      Copy(_s);
  }

  // Метод тестирования
  void Print(string msg)
  {
    cout << msg << " => " << s << endl;
  }

  // Деструктор
  ~Value()
  {
    delete[] s;
  }

  // Конструктор копирования – делегирует полномочия конструктору с 1 параметром
  Value(const Value<char*>& obj) : Value<char*>(obj.s) { }

  // Оператор копіювання
  Value& operator=(const Value<char*>& obj)
  {
    // 1. Освободить память, выделенную под предыдущую строку
    delete[] s;

    // 2. Выделить память под новую строку, копирование s<=obj.s
    Copy(obj.s);

    // 3. Вернуть текущий объект
    return *this;
  }

  // Метод Get() - возвращает указатель на char
  char* Get() { return s; }
};

// 3. Полная специализация для void*,
// здесь параметры шаблона <> отсутствуют так же
template <>
class Value<void*>
{
private:
  void* p;

public:
  Value() { p = nullptr; }
  Value(void* _p) : p(_p){ }

  // Метод Get()
  void* Get() { return p; }
};

// 4. Частичная специализация,
// которая используется для всех указателей типа T
template <class T>
class Value<T*> //: private Value<void*>  // все указатели void* можно привести к любому T*
{
private:
  T* p;

public:
  Value(T* p)
  {
    // делаем копию
    this->p = new T;
    *(this->p) = *p;
  }

  void Print(string msg)
  {
    cout << msg << " = " << *p << endl;
  }

  // Конструктор копирования
  Value(const Value& obj) : Value(obj.p)
  {}

  // Оператор копирования
  Value& operator=(const Value& obj)
  {
    *p = *obj.p;
    return *this;
  }

  // Деструктор
  ~Value()
  {
    delete p;
  }

  // Метод Get() - не возвращает копию
  T* Get() { return p; }
};

int main()
{
  // Тест класса Value<T>
  // 1. Обычное объявление шаблонного класса
  Value<int> vi1 = 22;
  vi1.Print("vi1");

  // 2. Полная специализация для типа char*
  // 2.1. Объявить экземпляр
  Value<char*> vc1 = "Hello, world!";
  vc1.Print("vc1");

  // 2.2. Тест конструктора копирования
  Value<char*> vc2 = vc1;
  vc2.Print("vc2");

  // 2.3. Тест оператора копирования
  Value<char*> vc3;
  vc3 = vc1;
  vc3.Print("vc3");

  // 2.4. Метод Get()
  char* vc4 = vc2.Get();
  cout << "vc4 = " << vc4 << endl;

  // 3. Полная специализация для типа void*
  // Здесь вызывается полная специализация для void*
  Value<void*> p2;
  void* pvoid = p2.Get();

  // 4. Частичная специализация для типа int*
  // 4.1. Объявить экземпляр
  Value<int*> pi1 = new int(50);
  pi1.Print("pi1");

  // 4.2. Тест конструктора копирования
  Value<int*> pi2 = pi1;
  pi2.Print("pi2");

  // 4.3. Тест оператора копирования
  pi2 = pi1;
  pi2.Print("pi2");

  // 4.4. Метод Get()
  int* pi = pi2.Get();
  cout << "pi = " << *pi << endl;
}

 


Связанные темы