为什么要在C++标准库中为列表/树节点使用基类?

rjjhvcjd  于 9个月前  发布在  其他
关注(0)|答案(1)|浏览(86)

libstdc++libc++中,列表(例如listforward_list)和其他树的内部节点(至少)由两部分构成:节点基类和节点类本身。例如,而不是:

struct sl_full_node {
  sl_full_node *p;
  int value;
};

字符串
.我们会看到这样的东西:

struct sl_node_base {
  sl_node_base *p;
};

struct sl_node : sl_node_base {
  int value;
};


在2008 here的单链表提案中,可以找到这种习惯用法的一个简单、独立的例子。这种方法有什么好处?

mi7gmzs6

mi7gmzs61#

所有的STL容器都需要提供一个end() sentinel。这意味着,如果支持双向迭代,必须有一个有效的结束后位置,可以通过迭代器接口对该位置执行递减操作。在这一部分中,只处理基于节点的容器,允许双向迭代,因此std::forward_list容器将被暂时忽略。
如果end() sentinel简单地由任何sentinel值表示,例如空指针,则不可能执行上述递减操作以移动到前一个节点。解决此问题的一种方法是使用“虚拟节点”。
在libstdc和libc实现中,这个节点是静态分配的,以便它在对象的整个生命周期中都存在于容器中,并且即使序列完全为空,也允许有效的过尾位置。重要的是要注意,单词“序列”用于指代容器中所有值的有序迭代,而不管节点是线性排列还是以更复杂的图(std::set)结构化。
如果通过分配一个完整的节点来实现这个结果,这将导致两个副作用:

  • 虚拟节点所占用的空间将不受指针数量的限制,指针是允许与其他节点链接所需的唯一成员属性,但将与类型T大小成比例;
  • 如果类型T直接存储在节点中,则当构造对象时,应该找到方法以避免T对象构造。

最后一个问题可以通过用对齐存储替换类型T来解决,但这不会解决占用空间的问题。这种情况使得有必要为虚拟节点引入一个特定的结构,它只包含指向完整节点的指针。然而,这种结构的创建将导致指向全节点的指针和指向伪节点的指针之间的互操作性问题。此外,全节点可以被解释为已经添加了存储的伪节点的事实表明它们之间的继承关系是有意义的。
因此,它被实现为表示基本节点的结构,并且可以被示例化以获得伪节点,以及从表示完整节点的前一个结构导出的结构。
范例:

struct _List_node_base
    {
      _List_node_base* _M_next;
      _List_node_base* _M_prev;
    };

    template <typename _Tp>
    struct _List_node : public _List_node_base
    {
      _Tp _M_data;
    };

字符串
类似的推理可以应用于std::forward_list容器,但是,它需要一个before_begin() sentinel,可以通过迭代器接口对其执行递增操作。

相关问题