ШАБЛОНЫ C ++
Контейнер C ++ с условно защищенным доступом
Разработайте индексатор и итератор на основе прокси для вашего контейнера
Это второй выпуск серии о практическом проектировании C ++. В прошлый раз мы обсуждали создание аккумуляторного контейнера (проверьте здесь). Сегодня поговорим о доступе и итерациях. Хочу поделиться с вами интересным паттерном, который я использовал в одном из своих проектов.
Когда мы говорим об обработке содержимого контейнера, мы имеем в виду две ситуации. Первый - это итератор с полным доступом для чтения и изменения элементов контейнера. Второй - это постоянный итератор, не имеющий права изменять содержимое. Хотя что, если нам нужно изменить значение элемента, но для этого нужно сохранить какое-то условие?
Давайте посмотрим на пример. У нас есть простой контейнер на основе массива, который должен хранить только значения из интервала [0, 1]
. Если мы начнем с простейшей формы изменения контента - оператора доступа к индексу (или оператора произвольного доступа, или индексатора), в какой-то момент мы должны увидеть следующую картину:
Таким образом, вы можете просто представить одну и ту же картину для итератора и оператора разыменования.
Зачем нам это делать?
Это момент, когда вы должны сказать: «Эй, просто добавьте специализированный метод установки со встроенным условием, и работа будет выполнена!». Хорошо, но я полагаю, что мы хорошие разработчики и хотим создать контейнер, совместимый с требованиями STL, со всеми соответствующими итераторами и операторами. Даже если нет, вам все равно понадобится какой-то инструмент для обработки каждого элемента, поэтому нет способа избежать разработки итератора.
Вы можете указать на несколько альтернатив.
- очень навороченный шаблон, который предотвратит присвоение несоответствующих значений. Отрицательный результат состоит в том, что «плохие» случаи не собираются. И, думаю, может получиться практически нечитаемый шаблон.
- специализированный класс с правильным типом данных. Вам будет приятно поработать с новой иерархией, преобразованием типов, утверждениями, конкретными конструкторами и т. Д.
- внешнее состояние. Мы даже не будем обсуждать эту идею, потому что вы можете представить себе проблемы масштабирования, повторного использования и переноса такого кода.
Все, что нам нужно, это простое, идеально скомпилированное решение без конвертации.
Прокси-класс
Хорошо, нам понадобится дополнительный класс. Хотя, как мы обсуждали ранее, это не новый тип данных. Этот класс будет частью нашего контейнера и будет представлять собой легкую оболочку для хранения ссылки на экземпляр и метода с проверяемым условием. Чем-то он может напоминать прокси-класс для вектора bool.
Идея очень проста:
- Перегруженные операторы контейнера возвращают ссылку на одноразовый прокси-объект;
- Прокси-объект принимает желаемое значение и проверяет условие;
- Если проверка пройдена - укажите значение внутри контейнера.
Поскольку идея очень проста, теперь мы можем предоставить код:
Реализация проста, вы можете захотеть добавить больше перегрузок арифметических операторов. Как вы могли заметить, мы также добавили оператор свойства для быстрого преобразования в базовый тип контейнера. Также вы можете реализовать любое условие, политику проверки, генерацию исключений и так далее. В качестве примера я следовал идее 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 ++. В ожидании вы можете поделиться своим опытом.