C++ 集合迭代器中的不完整类型

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;

我自己编写了一个自定义STL样式的容器,它在内部使用AVL树来组织数据。现在,在一个项目中,我希望有一个迭代器作为它的成员:

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依赖项

可以将间接寻址放置在您喜欢的任何位置,但是对于链接结构,通常最方便的位置是结构的
根/头/尾。这可以防止使用链接结构的客户机担心这些递归/循环类型依赖关系

间接成本 我经常听到的一件事是“间接的成本”,似乎这是非常昂贵的。这完全取决于内存访问模式以及它们与内存的关系