是否有类似Clojure中的持久化数据结构在C++中的实现?
immer
库 是一个相当全面的例子,它专门受到clojure的启发。几年前,我听了约翰·卡马克的演讲后,他一直在跳上函数式编程的列车,我感到非常兴奋,然后自己写了一个。他似乎能够想象一个围绕不可变数据结构旋转的游戏引擎。虽然他没有详细说明,而且似乎只是他脑海中的一个模糊想法,但他认真考虑这个想法,并且似乎认为开销不会大幅削减帧速率足以让我对探索这个想法感到兴奋。
实际上,我将其用作某种优化细节,这可能看起来有些矛盾(不可变性会有开销),但我指的是特定的上下文。如果我绝对想要做到这一点:
// We only need to change a small part of this huge data structure.
HugeDataStructure transform(HugeDataStructure input);
我希望这个函数不会产生任何副作用,以便它能够线程安全且永远不容易被误用。因此,我别无选择,只能复制庞大的数据结构(可能跨越一千兆字节)。
在这种情况下,拥有一小组不可变数据结构非常有帮助,因为它通过仅浅复制和引用未更改的部分使上述情况相对便宜。话虽如此,我主要使用一个不可变数据结构,它基本上是一个随机访问序列,如下所示:
正如其他人提到的那样,确实需要一些关注和调整以及全面的测试和许多VTune会话来使其线程安全和高效,但是在我付出努力之后,它确实使事情变得更加容易。
除了使用这些结构创建无副作用函数时的自动线程安全性外,您还可以获得像非破坏性编辑、轻松撤消系统、轻松异常安全性(无需通过作用域保护回滚引起的任何副作用的函数来回滚),以及允许用户复制和粘贴数据和实例而不占用太多内存,直到/除非他们修改所粘贴内容的额外奖励。实际上,我发现这些奖励在日常使用中比线程安全性更有用。
我使用“暂态”(又名“生成器”)作为表达对数据结构的更改的方法,如下所示:
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();
}
实现持久化数据结构的主要难点确实是缺乏垃圾回收机制。
如果没有适当的垃圾回收机制,那么你可以得到一个糟糕的机制(即引用计数),但这意味着你需要特别小心地避免创建循环引用。
这改变了结构的核心。例如,考虑二叉树。如果你创建一个节点的新版本,那么你需要其父节点的新版本来访问它(等等...)。现在,如果关系是双向的(孩子<->父母),那么实际上你将复制整个结构。这意味着你将有一个父亲->孩子的关系,或者反过来(不太常见)。
我可以想到实现二叉树或B-Tree。但对于如何获得适当的数组,我几乎看不到头绪。
另一方面,在多线程环境中拥有高效的持久化数据结构确实是非常重要的。
shared_ptr
方案就足够了,无需全面使用垃圾回收器。 - Matthieu M.
A
,则可以创建指向A
的B
,但是您需要修改A
才能创建循环。持久化数据结构是不可变的,因此在C++中实现它们只需要一个简单的引用计数即可。 - ivant