【发布时间】:2016-02-27 05:29:36
【问题描述】:
我为自己编写了一个自定义 STL 风格的容器,它在内部使用 AVL 树来组织数据。现在,在一个项目中,我希望有一个迭代器作为成员:
class vertex {
...
avl_tree<vertex>::iterator partner;
...
}
但是,我得到了错误:
error: ‘avl_tree<T, A>::node::data’ has incomplete type
T data;
^
根据我在 SO 和其他网站上阅读的内容,vertex 在完全定义之前是一个不完整的类型。 avl_tree<T,A>::node 是我用来管理树的私有结构,它的成员中有T data;,但是如果T 不完整,则这是非法的。奇怪的是,当我改用std::list 时,没有这样的问题,我理解这是未定义的行为。
有没有解决这个问题的简单方法? avl_tree<T,A>::iterator 内部只维护一个指针node *ptr,因为指针具有固定大小,所以对于不完整类型来说这应该不是问题。但我不想将node 类暴露给public,我想使用iterators。尽管如此,iterator 将始终具有相同的大小,无论模板参数如何,有没有办法强制编译器承认这一事实?
结构概览:
template <typename T, typename A = std::allocator<T> >
class avl_tree {
private:
class node {
public:
T data;
avl_tree *tree;
short depth;
size_type n;
node *parent;
node *left_child;
node *right_child;
};
public:
class iterator {
private:
node *ptr;
};
private:
using NodeAlloc = typename std::allocator_traits<A>::template rebind_alloc<node>;
NodeAlloc alloc;
node root;
};
完整代码可在GitHub获取。
【问题讨论】:
-
您的
avl_tree是否直接存储节点(不是指向它的指针)- 例如:对于根节点?更多代码 sn-ps 显示您的节点/树/迭代器的数据成员将不胜感激(可以省略方法 - 就像“结构概述”一样)。 -
递归对象定义始终是我看到通过间接解决的问题,例如
avl_tree<vertex*>::iterator partner或avl_tree<boost::recursive_wrapper<vertex>>::iterator partner。后者只是简单地包装了一个动态分配的对象,为您返回值语义,该对象旨在准确处理此类情况。哪种解决方案最适合您取决于您,但基本上对于这样的递归数据结构,您需要间接。 -
简单的方式 IMO 以极小的成本为根的效率:在那里添加间接以中断递归。存储
node* root。它应该只产生可以忽略不计的差异,以换取整个容器的更大通用性。如果你测量它并发现它作为一个热点足够讨厌,你可以为整个树实现一个固定的分配,并有可能改进一切(链接结构可以从一个不太通用的分配中受益匪浅)。 -
@Ike 我确实最终将 root 更改为
node*。我无法衡量性能上的任何差异。由于树无论如何都存储在堆上,因此根处的附加间接性并没有受到伤害,它假设。 -
@Jonas 在根处切断链接是一个很好的开始策略——如果你想要更快的速度,可以关注分配器。这也是
std::list在这种上下文中可以避免递归类型依赖的原因:它存储指向尾部和头部的指针,因此不需要节点的完整类型定义(同样因此不需要T) 在实例化时的完整类型定义。与std::map类似,它通常存储一个指针。
标签: c++ templates stl incomplete-type