我自己动手制作,但是有一个immer库作为一个相当全面的示例,它特别受clojure启发。听了约翰·卡马克(John Carmack)的演讲后,我兴奋不已,回过头几年了。他似乎能够想象一个围绕不可变数据结构的游戏引擎。虽然他没有详细说明,但脑子里似乎只有一个模糊的主意,但他认真考虑这一事实,似乎并不认为开销会大幅降低帧速率,这一事实足以使我兴奋关于探索这个想法。
实际上,我将其作为某种优化细节使用,这看起来似乎很矛盾(不变性存在开销),但是我的意思是在特定上下文中。如果我绝对想这样做:
// We only need to change a small part of this huge data structure.
HugeDataStructure transform(HugeDataStructure input);
Run Code Online (Sandbox Code Playgroud)
...而且我绝对不希望该函数引起副作用,以便它可以是线程安全的并且永远不容易被滥用,然后我别无选择,只能复制巨大的数据结构(可能跨越一个千兆字节)。
在这种情况下,我发现拥有一个小型的不可变数据结构库非常有用,因为通过浅拷贝和引用未更改的部分,上述方案相对便宜。就是说,我主要只是使用一个不变的数据结构,它基本上是一个随机访问序列,如下所示:
正如其他人提到的那样,它确实需要进行一些仔细的调试和调整以及全面的测试以及许多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();
}
Run Code Online (Sandbox Code Playgroud)
我什至有一个不可变的图像库,可用于图像编辑以简化无损编辑。它通过将图像视为图块来使用与上述结构类似的策略,如下所示:
当瞬态被修改并且我们得到一个新的不变量时,只有更改的部分才是唯一的。其余的图块是浅复制的(仅32位索引):
我确实在诸如网格和视频处理之类的性能至关重要的领域中使用了它们。关于每个块应存储多少数据有一些微调(太多,我们浪费处理和内存深度复制太多的数据,太少了我们浪费处理和内存浅拷贝太多的线程锁定次数更多的指针)。
我不将它们用于光线追踪,因为这是可以想象到的最极端的性能关键领域之一,并且用户可以注意到最细微的开销(它们实际上基准测试并注意到性能差异在2%的范围内),但是大多数时间,它们足够高效,当您可以左右左右复制这些庞大的数据结构以简化线程安全性,撤消系统,无损编辑等工作而又不必担心爆炸性的内存使用时,这是一个了不起的好处并且明显的延迟花费在深度复制所有内容上。
如果我正确地理解了这个问题,那么您所寻求的是复制对象的能力,而无需在完成时实际支付复制费用,而仅在需要时才进行。对任一对象的更改都可以在不损坏另一个对象的情况下完成。这称为“写入时复制”。
如果这就是您正在寻找的,那么可以使用共享指针在 C++ 中相当轻松地实现(请参阅 Boost 中的 shared_ptr,作为一种实现)。最初,副本将与源共享所有内容,但是一旦进行更改,对象共享指针的相关部分就会被指向新创建的深度复制对象的其他共享指针所取代。(我意识到这个解释是模糊的 - 如果这确实是你的意思,答案可以详细说明)。