C++ 在执行“a”时遇到问题;绳索“;C+中的数据结构+;

C++ 在执行“a”时遇到问题;绳索“;C+中的数据结构+;,c++,data-structures,ropes,C++,Data Structures,Ropes,我正在尝试建立一个数据结构。它是一种二叉树,即递归数据结构 rope的用途是快速拆分和连接,这意味着您可以避免复制整个rope。 因此,例如,用户应该能够说rope1+rope2,并期望在~对数时间内得到结果 但是,这带来了一个问题: 如果修改了绳索,其父绳索也会间接修改。 因为我的目标是使绳索成为字符串的替代品,这是不可接受的 我的解决方案是:无论何时对rope进行任何更改,我都会创建一个新节点,只需稍作更改,而不修改旧节点 从理论上讲,我认为这会很有效 但实际上,它涉及到(几乎?)对字符串的

我正在尝试建立一个数据结构。它是一种二叉树,即递归数据结构

rope的用途是快速拆分和连接,这意味着您可以避免复制整个rope。
因此,例如,用户应该能够说
rope1+rope2
,并期望在~对数时间内得到结果

但是,这带来了一个问题:
如果修改了绳索,其父绳索也会间接修改。
因为我的目标是使
绳索
成为
字符串
的替代品,这是不可接受的

我的解决方案是:无论何时对
rope
进行任何更改,我都会创建一个新节点,只需稍作更改,而不修改旧节点

从理论上讲,我认为这会很有效

但实际上,它涉及到(几乎?)对字符串的每次修改的堆分配。
即使是一个字符的更改也会产生一个新的堆对象,它不仅自身速度慢,而且还会显著降低内存局部性,对性能产生非常负面的影响

我应该如何着手解决这个问题

但在实践中,它涉及(几乎?)字符串的每一次修改的堆分配

如果您想避免频繁的堆分配性能问题,那么我建议为您的类使用一个内存池来分配一块内存,并且只需要在内存满时从操作系统请求一个新的分配,这种情况应该很少发生。然后,您可以优化对内存池的访问,以分配小块数据类型,如
char


<> Andrei Alexandrescu在《现代C++设计》一书中有一个小块内存分配器的例子,

传统的方法是拷贝写:为此,需要重新分配每个分配的节点。 如果修改的节点的refcount为1(没有其他人引用它),则不需要复制它

这方面的实际问题是有效地将突变操作与非突变操作分离:

    char& rope::operator[] (std::string::pos)
可能会改变引用的字符,但在实际不会改变的情况下,没有简单的方法强制选择常量重载。这意味着您必须假设将发生突变,并可能触发不必要的副本,或者返回一些代理,从而重载字符转换和赋值

这种方法曾尝试用于早期实现的
std::string
(其中一个字符串相当于一个单节点rope)iirc,但不受欢迎;部分原因是突变问题,部分原因是如果您不得不担心多线程,COW和所需的重新计数将变得越来越昂贵


正如您所说,如果您的节点包含两种独立的状态类型,rope还有一个问题:它自己的字符串和对它的子节点的引用(因为这会导致refcount/子引用突变向上扩展树)

相反,如果字符只存储在叶节点上,并且对非叶节点进行完整复制(因此每个rope都有自己的“目录结构”),则仍然可以避免复制字符,并且refcounted共享状态更简单

这是否得到了所需的对数时间串联?也许不是:

  • 您必须复制所有非叶节点(并添加一个新的根节点),这些节点的数量是叶的数量
  • 但是,还必须增加叶引用计数,这是线性的
它看起来更接近线性时间还是对数时间取决于增加refcount与复制目录树的相对成本

但是,如果不这样做,您将获得快速的连接,但是如果任意字符访问必须将COW操作传播到树上,它们可能(不可预测地)退化为对数时间


我想,如果它适合您的用例,您可以实现移动连接:这可能会提供恒定的时间添加,并且您仍然可以避免额外的COW复杂性。

那么,您希望拆分和连接速度更快,但其他所有操作都要快吗?我认为这是一种“你不能既吃蛋糕又吃蛋糕”的情况。如果你有GCC,请查看
标题中的示例实现。“因为我的目标是让rope成为string的替代品,”你不能让
rope
成为
string
的替代品。根据定义,它们是具有两组不同操作的两种不同数据结构<代码>字符串,在C++11中,要求是连续的,而rope不是。如果堆也被另一个字符串引用,则只需要进行堆分配/复制。你很快就会陷入这样一种情况:所有这些拷贝最终都发生了,只是发生得比较晚,并且使用了更多的开销。@R.MartinhoFernandes:是的,我很害怕…+1表示重新计数,似乎是合理的。(仍在阅读其余部分——到目前为止看起来很棒,谢谢你提供的信息。)