c ++中是否存在类似于clojure中的持久性数据结构实现?
答案 0 :(得分:4)
我自己滚动但是immer
library是一个相当全面的例子,它特别受到了clojure的启发。几年前,我听到约翰卡马克的一次演讲,在那里他跳过了各种功能编程的潮流,让我兴奋不已。他似乎能够想象一个围绕不可变数据结构的游戏引擎。虽然他没有详细说明,虽然它看起来似乎是一个朦胧的想法,但他认真考虑它并且似乎并没有认为开销会急剧降低到帧速率这一事实足以令我兴奋关于探索这个想法。
我实际上将它用作某种优化细节,这可能看似矛盾(存在不可变性的开销),但我的意思是在特定的上下文中。如果我绝对想这样做:
// We only need to change a small part of this huge data structure.
HugeDataStructure transform(HugeDataStructure input);
...我绝对不希望该函数引起副作用,因此它可以是线程安全的,并且从不容易被滥用,那么我别无选择,只能复制庞大的数据结构(可能跨越一个千兆字节)。
在那里我发现有一个小的不可变数据结构库在这样的上下文中非常有用,因为它通过浅层复制和引用未更改的部分使上述场景相对便宜。也就是说,我大多只使用一个基本上是随机访问序列的不可变数据结构,如下所示:
正如其他人提到的,它确实需要一些关心和调整以及全面的测试和许多VTune会议,以使其线程安全和高效,但在我放入肘部油脂后,它确实使整个事情变得更加容易
除了自动线程安全之外,每当我们使用这些结构编写函数以避免副作用时,您还会得到非破坏性编辑,简单撤消系统,琐碎异常安全(无需回滚副作用)等内容通过功能中的范围保护,在特殊路径中不会导致任何情况),并允许用户复制和粘贴数据并实例化它而不需要占用太多内存,直到/除非他们修改他们粘贴的内容作为奖励。实际上我发现这些奖金每天都比线程安全更有用。
我使用'transients'(又名'构建者')来表达对数据结构的更改,如下所示:
Immutable transform(Immutable input)
{
Transient transient(input);
// make changes to mutable transient.
...
// Commit the changes to get a new immutable
// (this does not touch the input).
return transient.commit();
}
我甚至有一个不可变的图像库,我用它来进行图像编辑以简化非破坏性编辑。它使用与上述结构类似的策略,将图像视为瓦片,如下所示:
当修改瞬态并获得新的不可变时,只有更改的部分才是唯一的。其余的瓷砖是浅层复制的(只有32位索引):
我确实在网格和视频处理等性能相关的领域使用它们。关于每个块应该存储多少数据(太多并且我们浪费处理和内存深度复制太多数据,太少而且我们浪费处理和内存浅层复制太多指针和更频繁的线程锁,有一些微调) )。
我不会将这些用于光线追踪,因为这是可以想象的最极端的性能关键区域之一,用户可以注意到最微小的开销(他们实际上是基准测试并注意到2%范围内的性能差异),但是大部分时间,它们都足够高效,当您可以将这些巨大的数据结构左右复制以简化线程安全,撤消系统,非破坏性编辑等时,这是一个非常棒的好处,而不用担心爆炸性的内存使用和明显的延迟花费了深刻的复制一切。
答案 1 :(得分:2)
获取持久数据结构的主要困难确实是缺少垃圾收集。
如果你没有一个合适的垃圾收集方案,那么你可以得到一个糟糕的(即引用计数),但这意味着你需要格外小心不要创建循环引用击>
它改变了结构的核心。例如,想想二叉树。如果您创建新版本的节点,则需要其父版本的新版本才能访问它(等等...)。现在,如果关系是双向的(child< - > parent),那么你实际上将复制整个结构。这意味着您将拥有父母 - >儿童关系,或相反(不太常见)。
我可以考虑实现二叉树或B树。我几乎没有看到如何获得一个合适的数组。
另一方面,我同意在多线程环境中拥有高效的内容会很棒。
答案 2 :(得分:0)
如果我正确理解了这个问题,那么你所寻求的是能够复制一个对象,而不是在完成时实际支付复制费用,只有在需要时才能复制。可以在不损坏另一个对象的情况下对任一对象进行更改。 这被称为“写作时复制”。
如果这是您正在寻找的,可以使用共享指针在C ++中相当容易地实现(参见Boost的shared_ptr,作为一个实现)。 最初,副本将与源共享所有内容,但是一旦进行了更改,对象共享指针的相关部分将被指向新创建的深度复制对象的其他共享指针替换。 (我意识到这种解释是模糊的 - 如果这确实是你的意思,那么答案就可以详细说明了。)