C++ 集合迭代器中的不完整类型
我自己编写了一个自定义STL样式的容器,它在内部使用AVL树来组织数据。现在,在一个项目中,我希望有一个迭代器作为它的成员:C++ 集合迭代器中的不完整类型,c++,templates,stl,incomplete-type,C++,Templates,Stl,Incomplete Type,我自己编写了一个自定义STL样式的容器,它在内部使用AVL树来组织数据。现在,在一个项目中,我希望有一个迭代器作为它的成员: class vertex { ... avl_tree<vertex>::iterator partner; ... } 完整的代码在上可用。我想节点或迭代器类型没有问题。问题是您使用递归类型定义 class vertex { ... avl_tree<vertex>::iterator partner;
class vertex {
...
avl_tree<vertex>::iterator partner;
...
}
完整的代码在上可用。我想
节点
或迭代器
类型没有问题。问题是您使用递归类型定义
class vertex {
...
avl_tree<vertex>::iterator partner;
...
}
A
是您的avl_树
,B
是节点
,C
是迭代器
。误差是一样的
错误:“A::B::data”的类型不完整
现在有多种方法可以解决这个问题。第一个是将D
类型中的ad
类型更改为
A<D*>::C ad;
A::C ad;
但是,正如您所提到的,STL中的列表
(或向量
)没有这样的问题。这里是交易,A
中root
的类型应该是B*
或B&
,而不是B
。但是如果要使用B*
,则需要注意内存分配。完成类型
首先,让我们看一个简单的例子,说明定义UDT(用户定义类型)需要什么
给定上述代码,只有理解了struct Bar
的定义,编译器才能正确构建它。否则,它将无法知道如何将此条
数据成员与结构的实际大小(以及要添加多少填充以确保其正确对齐)对齐
因此,要以这种方式定义Foo
,同样需要定义Bar
。类型依赖项如下所示:
Foo->Bar
Foo->Foo->Foo->...
Foo->Node->Foo->Node->Foo->...
template <class T>
struct Tree
{
struct Node
{
T element;
};
Node root;
};
Tree->Node->T->...
list<T>->POD
node->T->...
如果我们将上述代码更改为:
struct Foo
{
struct Bar* bar;
};
。。。突然,我们看到了一个完全不同的场景。在这种情况下,Bar
可以是不完整的类型(声明但未定义),因为Foo
只存储指向它的指针。指针实际上是POD(普通的旧数据类型)。无论是指向条形图
还是指向Baz
,其大小和对齐要求都没有变化。因此,这里的类型依赖关系基本上是:
Foo->POD
因此,即使Bar
的定义未知,我们也可以编译此代码。当然,如果编译器遇到的代码试图访问Bar
的成员,或者构造它,或者执行任何需要有关Bar
的信息的操作,那么它将产生错误,除非此时Bar
的定义可用
循环/递归类型依赖项
让我们看一个递归类型依赖关系的简单示例:
struct Foo
{
struct Foo next;
};
对于这种情况,为了正确定义Foo
,我们必须正确定义Foo
。哎呀,无限递归。即使允许这样做,系统也会希望为Foo
分配无限量的内存。在本例中,类型依赖项如下所示:
Foo->Bar
Foo->Foo->Foo->...
Foo->Node->Foo->Node->Foo->...
template <class T>
struct Tree
{
struct Node
{
T element;
};
Node root;
};
Tree->Node->T->...
list<T>->POD
node->T->...
即使我们在中间引入了一种新类型,同样的问题仍然存在:
struct Foo
{
struct Node next;
};
struct Node
{
struct Foo element;
};
由于类型依赖项的周期性,这里仍然会出现编译器错误,如下所示:
Foo->Bar
Foo->Foo->Foo->...
Foo->Node->Foo->Node->Foo->...
template <class T>
struct Tree
{
struct Node
{
T element;
};
Node root;
};
Tree->Node->T->...
list<T>->POD
node->T->...
除此之外,我们还有鸡或蛋的问题<如果Foo
位于Node
之前,则不可能在定义Foo
时定义Node
,如果Node
位于Foo
之前,则不可能在定义Foo
时定义Node
为了打破这个循环,我们可以添加一个间接寻址:
struct Foo
{
struct Node* next;
};
struct Node
{
struct Foo element;
};
现在我们有:
Foo->POD
Node->Foo->POD
。。。这是有效的,避免了循环类型依赖,并且编译得很好
树示例
更接近您的树示例,让我们看看这样一个案例:
Foo->Bar
Foo->Foo->Foo->...
Foo->Node->Foo->Node->Foo->...
template <class T>
struct Tree
{
struct Node
{
T element;
};
Node root;
};
Tree->Node->T->...
list<T>->POD
node->T->...
如果T
不依赖于树
或节点
的定义,则可以很好地编译
然而,在您的例子中,T
是一个顶点
,它取决于存储顶点的节点所在树的类型定义。因此,我们有这种情况:
avl_tree<vertex>->node->vertex->avl_tree<vertex>->node->vertex->...
通过这种方式,我们切断了类型依赖关系,如下所示:
Tree->POD
Node->T->...
。。。或者,根据您的示例:
avl_tree<vertex>->POD
node->vertex->avl_tree<vertex>->POD
这里的迭代器很好,因为它存储了指向POD节点的指针。然而这里的问题是,我们试图访问avl_树
的一个成员,即使它只是一个类型名,这需要编译器有一个完整的avl_树
类型定义(它在我们可能喜欢的理想粒度级别上不太起作用)。这需要完整定义节点
,然后需要完整定义顶点
奇怪的是,当我使用std::list
时,并没有这样的问题
这是因为std::list
通常看起来像这样(给出或接受一些小的变化):
模板
班级名单
{
公众:
...
私人:
结构节点
{
节点*下一步;
节点*prev;
T元素;
};
...
节点*头;
节点*尾部;
};
此处的相关类型依赖项如下所示:
Foo->Bar
Foo->Foo->Foo->...
Foo->Node->Foo->Node->Foo->...
template <class T>
struct Tree
{
struct Node
{
T element;
};
Node root;
};
Tree->Node->T->...
list<T>->POD
node->T->...
list->POD
节点->T->。。。
破坏类型依赖关系
从上面我们可以看出,我们可以通过指针引入间接寻址来切断/中断类型依赖关系。这样,我们不再需要用户定义的类型定义,而是可以将UDT依赖项更改为简单的POD依赖项
可以将间接寻址放置在您喜欢的任何位置,但是对于链接结构,通常最方便的位置是结构的根/头/尾。这可以防止使用链接结构的客户机担心这些递归/循环类型依赖关系
间接成本
我经常听到的一件事是“间接的成本”,似乎这是非常昂贵的。这完全取决于内存访问模式以及它们与内存的关系