【问题标题】:C++ circular dependency with intrusive linked list具有侵入式链表的 C++ 循环依赖
【发布时间】:2012-10-02 22:39:20
【问题描述】:

我已经实现了这个侵入式链表:

template <class Entry>
struct LinkedListNode {
    Entry *next;
    Entry *prev;
};

template <class Entry, LinkedListNode<Entry> Entry::*NodeMember>
class LinkedList {
public:
    void init ();
    bool isEmpty () const;
    Entry * first () const;
    Entry * last () const;
    Entry * next (Entry *e) const;
    Entry * prev (Entry *e) const;
    void prepend (Entry *e);
    void append (Entry *e);
    void insertBefore (Entry *e, Entry *target);
    void insertAfter (Entry *e, Entry *target);
    void remove (Entry *e);

public:
    Entry *m_first;
    Entry *m_last;
};

...
template <class Entry, LinkedListNode<Entry> Entry::*NodeMember>
inline Entry * LinkedList<Entry, NodeMember>::next (Entry *e) const
{
    return (e->*NodeMember).next;
}
...

可以这样使用:

struct MyEntry {
    int value;
    LinkedListNode<MyEntry> list_node;
};

LinkedList<MyEntry, &MyEntry::list_node> list;
list.init();
MyEntry entry1, entry2;
entry1.value = 3;
list.append(&entry1);
entry2.value = 5;
list.prepend(&entry2);

一切正常,直到您需要两个包含彼此列表的对象:

struct MyEntry2;

struct MyEntry1 {
    int value;
    LinkedListNode<MyEntry1> node;
    LinkedList<MyEntry2, &MyEntry2::node> list;
};

struct MyEntry2 {
    int value;
    LinkedListNode<MyEntry2> node;
    LinkedList<MyEntry1, &MyEntry1::node> list;
};

每个MyEntry1 都持有一个MyEntry2 的列表,每个MyEntry2 只能出现在一个MyEntry1 的列表中;反之亦然。但是,这不会编译,因为成员指针 &MyEntry2::node 是在定义 MyEntry2 之前获取的:

prog.cpp:33:27: error: incomplete type 'MyEntry2' used in nested name specifier
prog.cpp:33:41: error: template argument 2 is invalid

这个有问题的布局实际上没有任何实际语义,这只是我发现的一个理论问题,它可能会限制通用链表的可用性。

有什么方法可以解决这个问题而不会使列表变得更加不切实际?

EDIT:这里所有数据结构的布局都是完全定义好的。这是因为 LinkedList 的数据成员不依赖有问题的 NodeMember 模板参数;只有函数可以。问题似乎是该语言要求知道 &MyEntry2::node ,即使它当时并不真正需要知道。

EDIT:必须可以使用此通用列表将结构添加到两个或多个列表中;这是 NodeMember 模板参数的用途 - 它指定要使用条目中的哪个 LinkedListNode。

【问题讨论】:

  • 我认为当时确实需要知道它。您正在引用一个尚未声明的成员,仅仅因为您没有创建结构的实例并不意味着您的编译器在解析该行时没有寻找该成员。当我只使用它的标识符而不是它的一个成员时,我只使用了结构的前向声明。也许我错了,但是循环依赖也让我困惑=P
  • 你也可以通过继承来实现这些钩子。这将解决问题并且更清洁 imo,无论如何它都是侵入性的。
  • 另外,您能否告诉我们您为什么不想使用 STL 列表?
  • @CrazyCasta 侵入性列表有很多理由。我认为这里没有必要。
  • @CrazyCasta 这个问题不是关于侵入式与非侵入式数据结构的。我的问题是关于侵入性列表,无论出于何种原因。我没有研究过 boost intrusive,但我怀疑他们也遇到了同样的问题。

标签: c++ templates linked-list circular-dependency intrusive-containers


【解决方案1】:

这是一个使用继承的实现,它不会受到影响 你的问题。

template <typename Entry>
struct LinkedListNode {
    Entry *next;
    Entry *prev;
};

template <class Entry>
class LinkedList {
public:
    void init ();
    bool isEmpty () const;
    Entry * first () const;
    Entry * last () const;
    Entry* next (Entry* e) const {
        return e->next;  
    }
    Entry * prev (Entry *e) const;
    void prepend (Entry *e);
    void append (Entry *e);
    void insertBefore (Entry *e, Entry *target);
    void insertAfter (Entry *e, Entry *target);
    void remove (Entry *e);
public:
    LinkedListNode<Entry> *m_first;
    LinkedListNode<Entry> *m_last;
};

struct MyEntry2;

struct MyEntry1 : public LinkedListNode<MyEntry1> {
    int value;
    LinkedList<MyEntry2> list;
};

struct MyEntry2 : public LinkedListNode<MyEntry2> {
    int value;
    LinkedList<MyEntry1> list;
};

这是一个解决方案,其中 LinkedList 有一个函子作为第二个 模板参数。我们使用带有模板的访问器函子 operator() 删除代码重复并延迟查找 姓名。 注意: 访问者实际上应该是一个成员并使用 空基优化。

template <class Entry>
struct LinkedListNode {
    Entry *next;
    Entry *prev;
};

template <class Entry, typename Func>
class LinkedList {
public:
    void init ();
    bool isEmpty () const;
    Entry * first () const;
    Entry * last () const;
    Entry * next (Entry *e) const {
      Func f;
      return f(e).next();
    }
    Entry * prev (Entry *e) const;
    void prepend (Entry *e);
    void append (Entry *e);
    void insertBefore (Entry *e, Entry *target);
    void insertAfter (Entry *e, Entry *target);
    void remove (Entry *e);

public:
    Entry *m_first;
    Entry *m_last;
};

struct MyEntry2;

struct node_m_access {
  template <typename T>
  LinkedListNode<T> operator()(T* t) const {
    return t->node;
  }
};

struct MyEntry1 {
    int value;
    LinkedListNode<MyEntry1> node;
    LinkedList<MyEntry2, node_m_access> list;
};

struct MyEntry2 {
    int value;
    LinkedListNode<MyEntry2> node;
    LinkedList<MyEntry1, node_m_access> list;
};

【讨论】:

  • 如何使结构成为多个独立链表的成员?
  • 成员指针参数的全部目的是允许这样做。
  • @AmbrozBizjak 您可能想将其添加到您的问题中。这是一个重要的要求。
  • @AmbrozBizjak 现在有第二个解决方案,使用带有模板化 operator() 的 accessor_functor。
  • 这很好用,谢谢。我还做了一些小修改,使其更易于使用(请参阅我的答案)。
【解决方案2】:

这个问题相当于尝试做:

struct MyEntry2;

struct MyEntry1 {
    MyEntry2 a;
};

struct MyEntry2 {
    MyEntry1 b;
};

在上述情况下,编译器在生成 MyEntry1 时需要知道 MyEntry2 结构的大小。在您的情况下,编译器在生成 MyEntry1 时需要知道 MyEntry2 中节点的偏移量。

我在 template-foo 方面没有经验,但我猜想你不想让 Entry 成为一个类,而是使用指向一个类的指针。

【讨论】:

  • 查看我的编辑。 LinkedList 的布局和大小可以在不知道 NodeMember 模板参数是什么的情况下确定。毕竟,LinkedList 只是一对指针(指针的大小在声明不完整后才知道)。看看 LinkedList 的声明如何并没有真正使用 NodeMember。
  • 但是它不知道::node是什么,因为你没有定义MyEntry2
  • 您发表评论时我正在编辑此内容,如果您仍然感到困惑,请告诉我。重要的是对 NodeMember* 的访问而不是 LinkedList 的大小。
  • @DeadMG 我明白为什么它不起作用。我在问如何使它工作。
  • 嗯,你不能,因为你所做的是一个基本的循环依赖。
【解决方案3】:

这是对 pmr 访问器解决方案的一个小修改,以减少样板的数量。诀窍是首先提供访问器的不完整“结构”声明,用这些实例化 LinkedList,然后通过从模板访问器类继承来完成访问器。

template <class Entry>
struct LinkedListNode {
    Entry *next;
    Entry *prev;
};

template <class Entry, class Accessor>
class LinkedList {
public:
    void init ();
    bool isEmpty () const;
    Entry * first () const;
    Entry * last () const;
    Entry * next (Entry *e) const {
        return Accessor::access(e).next;
    }
    Entry * prev (Entry *e) const;
    void prepend (Entry *e);
    void append (Entry *e);
    void insertBefore (Entry *e, Entry *target);
    void insertAfter (Entry *e, Entry *target);
    void remove (Entry *e);

public:
    Entry *m_first;
    Entry *m_last;
};

template <class Entry, LinkedListNode<Entry> Entry::*NodeMember>
struct LinkedListAccessor {
    static LinkedListNode<Entry> & access (Entry *e)
    {
        return e->*NodeMember;
    }
};

struct MyEntry2;
struct Accessor1;
struct Accessor2;

struct MyEntry1 {
    int value;
    LinkedListNode<MyEntry1> node;
    LinkedList<MyEntry2, Accessor2> list;
};

struct MyEntry2 {
    int value;
    LinkedListNode<MyEntry2> node;
    LinkedList<MyEntry1, Accessor1> list;
};

struct Accessor1 : LinkedListAccessor<MyEntry1, &MyEntry1::node> {};
struct Accessor2 : LinkedListAccessor<MyEntry2, &MyEntry2::node> {};

这样,当循环依赖没有问题时,甚至可以创建一个便利类:

template <class Entry, LinkedListNode<Entry> Entry::*NodeMember>
class SimpleLinkedList
: public LinkedList<Entry, LinkedListAccessor<Entry, NodeMember> >
{};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-24
    • 2021-09-03
    • 2016-05-03
    • 2021-08-15
    • 1970-01-01
    • 2012-04-24
    • 1970-01-01
    相关资源
    最近更新 更多