传统的方法是写时复制:为此,您需要重新计算每个分配的节点。
如果修改后的节点的引用计数为 1(没有其他人引用它),则不需要复制它。
这样做的实际问题是有用地将变异与非变异操作分开:
char& rope::operator[] (std::string::pos)
可能会改变引用的字符,但没有简单的方法可以强制选择 const 重载,而实际上它不会。这意味着您要么必须假设会发生突变,并可能触发不必要的复制,要么返回一些重载 char 转换和分配的代理。
这种方法被尝试用于 iirc 的早期实现std::string
(其中字符串相当于单节点绳索),但失宠了;部分原因是变异问题,部分原因是如果您不得不担心多线程,COW 和所需的引用计数会变得越来越昂贵。
正如您所说,如果您的节点包含两种独立类型的状态,则绳索还有一个问题:它自己的字符串和对其子节点的引用(因为这会导致 refcount/child 引用突变传播到树上)。
相反,如果字符仅存储在叶节点上,并且您对非叶节点进行了完整复制(因此每个绳索都有自己的“目录结构”),您仍然避免复制字符,并且引用的共享状态很多更简单。
这会得到你想要的对数时间连接吗?也许不是:
- 您必须复制所有非叶子节点(并添加一个新根),这些节点的数量是记录叶子的数量
- 您还必须增加叶引用计数,这是线性的
它看起来更接近线性时间还是对数时间取决于增加引用计数与复制目录树的相对成本。
但是,如果没有这个,您将获得快速连接,但如果必须将 COW 操作向上传播到树上,任意字符访问可能(不可预测地)退化为对数时间。
我想,如果它适合您的用例,您可以实现移动连接:这可能会给出恒定时间的加法,并且您仍然可以避免额外的 COW 复杂性。