ШАБЛОНЫ C ++

Контейнер C ++ с условно защищенным доступом

Разработайте индексатор и итератор на основе прокси для вашего контейнера

Это второй выпуск серии о практическом проектировании C ++. В прошлый раз мы обсуждали создание аккумуляторного контейнера (проверьте здесь). Сегодня поговорим о доступе и итерациях. Хочу поделиться с вами интересным паттерном, который я использовал в одном из своих проектов.

Когда мы говорим об обработке содержимого контейнера, мы имеем в виду две ситуации. Первый - это итератор с полным доступом для чтения и изменения элементов контейнера. Второй - это постоянный итератор, не имеющий права изменять содержимое. Хотя что, если нам нужно изменить значение элемента, но для этого нужно сохранить какое-то условие?

Давайте посмотрим на пример. У нас есть простой контейнер на основе массива, который должен хранить только значения из интервала [0, 1]. Если мы начнем с простейшей формы изменения контента - оператора доступа к индексу (или оператора произвольного доступа, или индексатора), в какой-то момент мы должны увидеть следующую картину:

Таким образом, вы можете просто представить одну и ту же картину для итератора и оператора разыменования.

Зачем нам это делать?

Это момент, когда вы должны сказать: «Эй, просто добавьте специализированный метод установки со встроенным условием, и работа будет выполнена!». Хорошо, но я полагаю, что мы хорошие разработчики и хотим создать контейнер, совместимый с требованиями STL, со всеми соответствующими итераторами и операторами. Даже если нет, вам все равно понадобится какой-то инструмент для обработки каждого элемента, поэтому нет способа избежать разработки итератора.

Вы можете указать на несколько альтернатив.

  • очень навороченный шаблон, который предотвратит присвоение несоответствующих значений. Отрицательный результат состоит в том, что «плохие» случаи не собираются. И, думаю, может получиться практически нечитаемый шаблон.
  • специализированный класс с правильным типом данных. Вам будет приятно поработать с новой иерархией, преобразованием типов, утверждениями, конкретными конструкторами и т. Д.
  • внешнее состояние. Мы даже не будем обсуждать эту идею, потому что вы можете представить себе проблемы масштабирования, повторного использования и переноса такого кода.

Все, что нам нужно, это простое, идеально скомпилированное решение без конвертации.

Прокси-класс

Хорошо, нам понадобится дополнительный класс. Хотя, как мы обсуждали ранее, это не новый тип данных. Этот класс будет частью нашего контейнера и будет представлять собой легкую оболочку для хранения ссылки на экземпляр и метода с проверяемым условием. Чем-то он может напоминать прокси-класс для вектора bool.

Идея очень проста:

  1. Перегруженные операторы контейнера возвращают ссылку на одноразовый прокси-объект;
  2. Прокси-объект принимает желаемое значение и проверяет условие;
  3. Если проверка пройдена - укажите значение внутри контейнера.

Поскольку идея очень проста, теперь мы можем предоставить код:

Реализация проста, вы можете захотеть добавить больше перегрузок арифметических операторов. Как вы могли заметить, мы также добавили оператор свойства для быстрого преобразования в базовый тип контейнера. Также вы можете реализовать любое условие, политику проверки, генерацию исключений и так далее. В качестве примера я следовал идее IntervalContainer, поэтому все верхние значения сохраняются как 1, а все нижние значения сохраняются как 0.

Индексатор

Мы уже видели код того, как мы хотим, чтобы он работал, поэтому сейчас я покажу вам его реализацию. Это перегрузка оператора, которая возвращает прокси-объект, который обертывает реальный доступ к элементу. Чтобы избежать проблем с преобразованиями, мы разделяем доступ только для чтения и доступ для записи.

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

Итератор

Мы хотим, чтобы следующий код компилировался и работал:

Как вы понимаете, пора реализовать итератор. В целом логика такая же, как и с перегрузкой индексатора: вернуть объект прокси-класса с некоторыми ограничениями.

У нас есть стандартная реализация типичных методов итератора, поэтому основное внимание уделяется оператору разыменования, который возвращает прокси-объект. Конечно, мы также добавим в основной класс методы begin() и end().

Пришло время кодирования. Завершим начальный пример классом IntervalContainer. Я сохраню простоту и реализую единственную функциональность, чтобы показать влияние прокси-класса. Вывод показывает изменения внутри контейнера после каждой операции.

Класс:

Использование:

Вывод:

0 0.1 1 0.3
0 0.3 1 0.9
0 0.9 1 1

Теперь у нас есть контейнер, который можно легко доработать в соответствии со спецификацией STL. Хотя вам следует использовать конкретный тип содержимого контейнера и избегать auto при доступе к элементам, это небольшая цена за такой удобный контейнер. И влияние уже минимизировано операторами свойств и арифметическими операторами прокси-класса. Конечно, условия и цель класса могут быть любыми. Хотя основной паттерн кондиционирования прокси-класса останется, его можно будет распространить и дальше, включив идиому PImple или что-то в этом роде.

Если вас интересует C ++ и дизайн контейнеров - добро пожаловать в мой Github, где вы можете найти рабочий пример для этой статьи:



Также убедитесь, что вы не пропустили предыдущий выпуск:



Следите за появлением следующих выпусков проектов C ++. В ожидании вы можете поделиться своим опытом.