如何在树访问器中实现每个节点的缓存
How to do per node caching in a tree visitor
我有一个应用程序,其中我想计算通过布尔运算(内部节点)组合的原语树(叶节点)的不同表示(网格,体素化,带符号距离函数,…)。
我的第一种方法是为每个不同的表示编写一个带有虚拟getter函数的抽象基类,并将中间结果缓存在各自的节点上,只要它们的子树没有变化(这会刷新它们的缓存)。
然而,我对树结构与每种不同表示的丑陋耦合感到不满意。为了减轻这个问题,我删除了抽象基类,并为每个表示设置了一个访问者。
这巧妙地将树与表示解耦,但给我留下了一个问题,我现在需要在其他地方缓存中间结果,这就是我的问题开始的地方。
TL;博士
如何在树的内部节点缓存(任意许多不同类型的)中间值,而不使树依赖于值类型?
我的方法需求提供了两个选择:
- 将数据存储在树中,但带有类型擦除
- 将数据存储在树外,并以某种方式将其"连接"到节点
第一个让我对一些效率问题感到困惑:我可以很容易地在节点中添加一个boost::any
(或类似的东西)的容器,但然后每个访问者都必须搜索整个容器以获取自己的数据。
第二个示例中的分离引入了保持当前树的缓存更新的问题。如果在树中有更改(删除,更改节点),缓存的值必须至少是无效的。我的直觉是使用哈希函数和unordered_map
但我也遇到了一些问题:
- 我不能使用树节点本身作为键,所以我需要引入另一个类,它只引用树节点并在树 中表示它们
- 引用
unordered_map
的键值需要擦除其引用被删除的所有条目,或者我们在unordered_map
中有一个悬空引用(/指针),这可能在rehash 上被触发。 - 树中的更改需要重建
unordered_map
,因为键可能已经更改
我错过了一些明显的解决方案吗?你喜欢哪种方法(为什么)?
我曾经遇到过类似的问题,我的解决方案如下:
- 让每个节点有一个唯一的标识符。
- 让每个节点都有一个版本号。使节点计算值无效的修改只会增加版本号。
- 让每个访问者有一个缓存映射,其中ID对是键,映射到版本/值对。
- 当(重新)遍历树时,在映射中查找节点的条目。如果版本正确,则使用缓存值。如果过期,计算新值并替换旧版本/值对。
起初,我使用节点的地址作为ID,但由于内存原因,我不得不重用子树并选择到节点的路径作为ID。这种路径的优点是它可以由每个访问者计算,而不需要存储在节点上。在我的例子中,每个节点最多可以有两个子节点,所以路径仅仅是一组左/右决定,它可以存储在一个简单的unsigned int中,并进行一些位移动(我的树从未达到32的深度,所以32位unsigned作为键就足够了)。
- 反向给定链表中的K节点
- 如果我只是不访问queue_front节点的子节点,而是将它们推到队列中呢?还是BFS吗
- Boost Graph Library,修复节点大小
- C++A*算法并不总是在路径中具有目标节点
- 如何找到2个单链表的公共节点
- 计算每个节点的树高,帮助我解释这个代码解决方案
- cmake更新缓存的变量
- 试图对缓存进行跨步测试,但程序并没有结束
- 为什么我的删除节点函数实际上没有删除节点?
- 我们可以删除链表中静态内存中的节点吗
- 如何在pugixml中获取节点的内部XML
- 缓存std::数组的选定元素,并在c++中自动保持其一致性
- 通过ccmake在cmake中缓存依赖选项
- 为什么我们要为avl树实现返回一个指向节点的指针,而不是void函数
- C++RapidXml-使用first_node()遍历以修改XML文件中节点的值
- 为什么C++对链表中的下一个节点使用指针,而像 C# 或 Java 这样的语言只使用类 Node 的名称?
- C++17 - 使用自定义分配器的节点提取/重新插入 - 适用于 clang++/libc++,但不适用于 libstd
- 节点 *temp; 和节点 *tmp = 新节点之间的差异
- 引擎节点:未定义的符号:_ZTV6Config
- 如何在树访问器中实现每个节点的缓存