Урок №178. Явная специализация шаблона класса

  Юрий  | 

  |

  Обновл. 28 Сен 2021  | 

 29561

 ǀ   8 

На предыдущем уроке мы говорили о том, как специализировать шаблон функции, чтобы при работе с одним типом данных была одна реализация функции, а при работе с другим типом данных — другая реализация функции. Оказывается, мы можем специализировать не только шаблоны функций, но и шаблоны классов.

Рассмотрим класс-массив, который может хранить 8 объектов:

Поскольку это шаблон класса, то он будет работать с любым типом данных:

Результат:

0
1
2
3
4
5
6
7
false
true
true
true
true
false
true
true

Хотя всё работает правильно, реализация Repository8<bool>, на самом деле, не столь эффективна, какой она могла бы быть. Поскольку все переменные должны иметь адрес, а ЦП не может дать адрес чему-либо меньшему, чем 1 байт, то размер всех переменных должен быть не менее 1 байта. Следовательно, каждая переменная типа bool занимает целый байт, хотя технически ей нужен только 1 бит для хранения значения true или false! Таким образом, переменная типа bool — это 1 бит полезной информации и 7 бит потраченного впустую места. Получается, что наш класс Repository8<bool>, который имеет 8 переменных типа bool, фактически работает только с 1 байтом данных, а остальные 7 байтов тратятся зря.

Выход есть: мы можем сжать 8 переменных типа bool в 1 байт, сэкономив при этом остальные 7 байтов. Однако для этого нам нужно будет изменить реализацию класса, заменив массив из 8 переменных типа bool (8 байтов) на 1 переменную типа unsigned char (1 байт). Мы могли бы, конечно, использовать для этого новый отдельный класс, но это неэффективно, и программисту придется помнить, что Repository8<T> работает со всеми типами данных, кроме bool, а при работе с bool следует вызывать Repository8Bool (неважно, какое имя будет у этого класса). Это лишняя работа, которую можно избежать, используя явную специализацию шаблона класса.

Специализация шаблона класса

Специализация шаблона класса (или «явная специализация шаблона класса») позволяет специализировать шаблон класса для работы с определенным типом данных (или сразу с несколькими типами данных, если есть несколько параметров шаблона).

Специализация шаблона класса рассматривается компилятором как полностью отдельный и независимый класс, хоть и выделяется как обычный шаблон класса. Это означает, что мы можем изменить в классе всё что угодно, включая его реализацию/методы/спецификаторы доступа и т.д.

Рассмотрим специализацию шаблона класса Repository8 для работы с типом bool:

Во-первых, начинаем с template<>. Ключевое слово template сообщает компилятору, что это шаблон, а пустые угловые скобки означают, что нет никаких параметров. А параметров нет из-за того, что мы заменяем единственный параметр шаблона (T, который отвечает за тип данных) конкретным типом данных (bool). Затем мы пишем имя класса и добавляем к нему <bool>, сообщая компилятору, что будем работать с типом bool.

Все остальные изменения — это просто детали реализации класса. Обратите внимание, вместо массива из 8 переменных типа bool (8 байтов) мы используем 1 переменную типа unsigned char (1 байт). Почему именно так? Об этом читайте на уроке №45 и на уроке №46.

Теперь при объявлении объекта класса Repository8<T>, где T не является bool, мы получим экземпляр общего шаблона класса, тогда как при объявлении объекта Repository8<bool>, мы получим экземпляр шаблона Repository8<bool>. Обратите внимание, мы не изменяли интерфейс класса, а оставили его открытым (каким он и был), в то время как язык C++ предоставляет нам возможность добавлять/изменять/удалять методы класса. Дело в том, что изменение интерфейса класса в специализациях шаблона не всегда приветствуется, так как программист может это дело забыть, а оно, в свою очередь, приведет к ошибкам.

Создадим объекты Repository8<T> и Repository8<bool>:

Как вы можете видеть, результат тот же, что и выше, где использовался общий шаблон класса Repository8:

0
1
2
3
4
5
6
7
false
true
true
true
true
false
true
true

Следует еще раз отметить, что сохранение открытого интерфейса в шаблонах ваших классов вместе со специализациями, как правило, является хорошей идеей, поскольку это упрощает использование классов и их логику, хоть и не считается строго необходимым.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (143 оценок, среднее: 4,92 из 5)
Загрузка...

Комментариев: 8

  1. Виктор:

    Эти каким математиком надо быть, что бы так рассчитывать побитовое возвращение. Проще не экономить память)

    1. mikasey:

      понятное дело так проще, но представь себе, что происходит симуляция большого количества физических точек, типа молекул или атомов(будем считать что их больше десятка милионов), с безумным количеством различных булов, там волей не волей, а если хочешь получить оптимизированную симуляцию, то лучше всетаки использовать побитовые вычисления.

      например "клеточная игра в жизнь" где у каждой "живой" есть проверка на наличие 8 и более соседних клеток

      1. Андрей:

        Вы не правы, памяти как раз хватит, а вот с такой оптимизацией будут лишние вычисления масок, что затормозит процесс

        1. DarkMatter:

          Размышления, конечно, очень интересные, но хотелось бы формального доказательства, почему побитовые операции дороже, чем экономия 7 байт на одном объекте. (если их, скажем, довольно много в какой-нибудь агрегирующей структуре данных.)

          Нужно сравнить оба варианта на практике.

        2. DarkMatter:

          Второй момент — использование побитовых сдвигов из статьи для микроконтроллеров, где количество памяти важнее тактов процессора. Хотя тут, конечно, сомнительно, применимо ли ООП и templates с программированием под микроконтроллеры.

          (Дайте знать, если применимо)

  2. rrrrr:

    cпециализация шаблона класса не работает

    'Repository8' is not a class template

    1. Фото аватара Юрий:

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

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *