У меня есть приложение, в котором требуется вычислить различные представления (сетка, вокселизация, функция расстояния со знаком, …) дерева примитивов (конечных узлов), которые объединяются с помощью логических операций (внутренние узлы).
Мой первый подход к этому состоял в том, чтобы написать абстрактный базовый класс с виртуальной функцией получения для каждого из различных представлений и кэшировать промежуточные результаты в соответствующих узлах, если не было изменений в их поддереве (что очистило бы их кэш).
Однако я был недоволен уродливой связью древовидной структуры с каждым из разных представлений. Чтобы облегчить это, я удалил абстрактные базовые классы и вместо этого настроил посетителя для каждого из представлений.
Это аккуратно отделило дерево от представлений, но оставило мне проблему с тем, что мне теперь нужно кэшировать промежуточные результаты где-то еще, и именно здесь начинается моя проблема.
Как мне кэшировать (произвольно много разных типов) промежуточные значения во внутренних узлах дерева, не делая дерево зависимым от типа значения?
Требования предлагают два варианта:
Первый оставляет меня озадаченным некоторой проблемой эффективности: я мог бы легко добавить контейнер boost::any
(или что-то эквивалентное) в узлах, но тогда каждому посетителю придется искать во всем контейнере свои собственные данные.
Разделение во втором вводит проблему поддержания актуальности кэша в текущем дереве. Если в дереве есть изменения (удаления, изменения узлов), кэшированные значения должны быть по меньшей мере недействительными. Моя интуиция состояла в том, чтобы использовать некоторую хэш-функцию и unordered_map
но я также столкнулся с некоторыми проблемами:
unordered_map
ключи требует стереть все записи, ссылки на которые удалены, или у нас есть свисающая ссылка (/ указатель) в unordered_map
который может быть вызван на перефразировкеunordered_map
потому что ключи могли изменитьсяЯ пропускаю какое-то очевидное решение этого?
Какой подход вы бы предпочли (и почему)?
Однажды у меня была похожая проблема, и мое решение было следующим:
Сначала я использовал адрес узла в качестве идентификатора, но по причинам памяти мне пришлось повторно использовать поддеревья и выбрать путь к узлу в качестве идентификатора. Преимущество такого пути состоит в том, что он может быть рассчитан каждым посетителем и не должен храниться в узле. В моем случае каждый узел может иметь не более двух дочерних элементов, поэтому путь — это просто набор левых / правых решений, которые могут быть сохранены в виде простого беззнакового целого с некоторым сдвигом битов (мои деревья никогда не достигали глубины 32). 32-битное без знака было более чем достаточно в качестве ключа).