C ++中的链接列表使用引用而不是指针

时间:2013-03-03 13:33:26

标签: c++ data-structures reference linked-list

假设我想创建一个不可修改的链表(即只能遍历它,一旦最初创建就不能添加或删除节点)。这可以通过以下方式轻松实现:

struct ListNode
{
  int value;
  ListNode* nextNode;
}

我的问题是......是否可以使用引用而不是指针?

struct ListNodeWithRefs
{
  int value;
  ListNodeWithRefs &nextNode;
}

我不确定它会提供任何性能提升但是......编码时出现了这个问题,到目前为止我的回答是 no 但是我可能会遗漏一些东西。

原则上,没有什么可以阻止你使用引用,并构建像这样的列表元素:

ListNodeWithRefs::ListNodeWithRefs(ListNodeWithRefs &next):
  nextNode(next)
{}

但是有一个鸡和蛋的问题,因为next也强制其next元素在其创建时存在等等......

注意:我认为我的问题也适用于将列表定义为:

struct ListNodeConst
{
  int value;
  const ListNode* nextNode;
}

8 个答案:

答案 0 :(得分:3)

这是函数语言中 cons-list 的典型代码:

data List a = Empty | Node a (List a)

诀窍是,List a是完整类型,可以将 引用到Empty或另一个节点(这就是它可以终止的原因)。

为了在C ++中实现这一点,您可以利用 union (但它没有得到很好的支持)或多态。< / p>

template <typename T>
struct ListBase {
    enum class Kind { Empty, Node };
    explicit ListBase(Kind k): _kind(k) {}

    Kind _kind;
};

然后:

template <typename T>
struct EmptyList: ListBase<T> {
    EmptyList(): ListBase<T>(Kind::Empty) {}
};

template <typename T>
struct ListNode: ListBase<T> {
    ListNode(T const& t, ListBase<T>& next):
        ListBase<T>(Kind::Node), _value(t), _next(next) {}

    T _value;
    ListBase<T>& _next;
};

现在,你没有鸡肉和鸡肉。鸡蛋问题不再;从EmptyList<T>的实例化开始。

注意:基类中_kind的存在不是那个OO,但它通过标记使用哪种替代方法使功能性示例更接近。

答案 1 :(得分:2)

通过sbi看一下这个例子,似乎有效:https://stackoverflow.com/a/3003607/1758762

// Beware, un-compiled code ahead!
template< typename T >
struct node;

template< typename T >
struct links {
  node<T>& prev;
  node<T>& next;
  link(node<T>* prv, node<T>* nxt); // omitted
};

template< typename T >
struct node {
  T data;
  links<T> linked_nodes;
  node(const T& d, node* prv, node* nxt); // omitted
};

// technically, this causes UB...
template< typename T >
void my_list<T>::link_nodes(node<T>* prev, node<T>* next)
{
  node<T>* prev_prev = prev.linked_nodes.prev;
  node<T>* next_next = next.linked_nodes.next;
  prev.linked_nodes.~links<T>();
  new (prev.linked_nodes) links<T>(prev_prev, next);
  next.linked_nodes.~links<T>();
  new (next.linked_nodes) links<T>(next, next_next);
}

template< typename T >
void my_list<T>::insert(node<T>* at, const T& data)
{
  node<T>* prev = at;
  node<T>* next = at.linked_nodes.next;
  node<T>* new_node = new node<T>(data, prev, next);

  link_nodes(prev, new_node);
  link_nodes(new_node, next);
}

答案 2 :(得分:2)

没有。原因:

  1. 如果nextNode是引用,则无法插入节点。
  2. 如果这是list tail,nextNode应该引用什么?

答案 3 :(得分:2)

列表如何结束?

您至少需要两种类型:结束而不是结束。您还需要终身管理。以及哪种类型的运行时或静态知识。

可以完成一个完全静态的实现,其中每个节点都是自己的类型,知道它到底有多远。

或者你可以只使用一个未初始化的缓冲区,并以相反的顺序创建它的元素。

圆圈也是可能的。使第一个引用引用您构造的最后一个元素。

答案 4 :(得分:0)

正如@Vlad所说,引用的问题是你需要一个最终的对象。 好消息是,原则上,如果你有使用它,你仍然可以有一个循环列表。 这是一个基本的东西,如果“next”元素是一个不可为空的引用意味着总是有一个下一个元素,也就是说,列表要么是无限的,要么更现实地说,它会自己关闭或者关闭到另一个列表中。 / p>

进一步锻炼非常有趣并且奇怪。 基本上,似乎唯一可能的是定义一个节点的等价物(也代表该列表)。

template<class T>
struct node{
    T value; // mutable?
    node const& next;
    struct circulator{
        node const* impl_;
        circulator& circulate(){impl_ = &(impl_->next); return *this;}
        T const& operator*() const{return impl_->value;}
        friend bool operator==(circulator const& c1, circulator const& c2){return c1.impl_ == c2.impl_;}
        friend bool operator!=(circulator const& c1, circulator const& c2){return not(c1==c2);}
    };
    circulator some() const{return circulator{this};}
};

元素必须存在于堆栈中且列表是静态的(好吧,引用无论如何都不能重新绑定)并且链接必须是const引用! 最终value可以显然mutable显然(可能安全吗?)。 (此时,人们想知道这与模数索引的堆栈数组引用有什么不同。)

只有一种方法可以构造node / list对象,即将其与自身(或与其他预先创建的节点)关闭。因此,结果列表是圆形或“rho”形状。

    node<int> n1{5, {6, {7, n1}}};
    auto c = n1.some();
    cout << "size " << sizeof(node<int>) << '\n';
    do{
        cout << *c << ", ";
        c.circulate();
    }while(c != n1.some()); //prints 5, 6, 7

我无法创建一个不易于构造的节点(聚合?)。 (添加任何类型的基本构造函数都会导致我无法理解的原因,包括gccclang)。 出于同样的奇怪原因,我无法将节点封装在“容器”对象中。 所以制作一个可以像这样构造的物体对我来说是不可能的:

circular_list<int> l{1,2,3,4}; // couldn't do this for obscure reasons

最后,由于无法构造适当的容器,因此不清楚该对象的语义是什么,例如当两个“列表”相等时?什么并不意味着分配?或者在不同大小的列表之间分配?

这是一个非常矛盾的对象,显然没有一般的价值或参考语义。

欢迎任何评论或改进!

答案 5 :(得分:0)

我可能记不清了,但这行得通

    struct Node;
struct Node {

    using link = std::reference_wrapper<Node>;

    Node( char data_  =  0) 
        : next({*this})
        , data( data_ == 0 ? '?' : data_ ) 
    {}

    bool is_selfref() const noexcept {
        return (this == & next.get());
    }

    char data;
    link next;
};

常规测试

 Node A('A');     
 Node B('B');     
 Node C('C');     
 assert( A.is_selfref() == B.is_selfref() == C.is_selfref());

 A.next = B;  B.next = C;

 assert(! A.is_selfref() && ! B.is_selfref() );
 assert(  C.is_selfref() );

 assert( 'A' == A.data );
 assert( 'B' == A.next.get().data );
 assert( 'C' == A.next.get().next.get().data );

 // C.next == C

 // for those who feel safe seeing the END
 Node END(127);
 C.next = END;

当然,只要所有Node都在作用域内,我们在这里就可以了。否则不行。奇怪而美妙。实用程序非常有限?

答案 6 :(得分:0)

那确实很棘手,但这可行:

#include <iostream>
#include <typeinfo>

class Last {
  public:

    int x;
    int last;

    Last(int i) {
      std::cout << "parent constructor(" << i << ")\n";
      x = i;
      last = 1;
    }
};

struct fourptr {
    int v1, v2;
    void *p1, *p2;
    };

class chain : public Last {
  public:

    chain(int i) : Last(i) {
    std::cout << "child constructor(" << i << ")\n";
    last = 0;
    }

    void viewandnext() {
      struct fourptr *fp = (struct fourptr *) this;
      std::cout << x << ", this = " << this
                << ", sizeof *this = "<< sizeof * this
                << ", * this = {" << fp->v1 << ", " << fp->v2 << ", "
                << fp->p1 << ", " << fp->p2 << "}"
                << "\n";
      if (last == 0) ((chain &)next).viewandnext();
    }

  Last & fn(int x) {
    Last * e = (x>0) ? new chain(x-1) : new Last(x-1);
    return *e;
  }

    Last & next = fn(x); // This is not a pointer but a reference
};



int main(void) {
  chain &l = *(new chain(8));
  std::cout << "sizeof l = "<< sizeof l << "\n";
  l.viewandnext();
}

答案 7 :(得分:0)

避免带有引用的列表出现先天问题的一个简单方法是记住,首先分配对象内存,然后调用构造函数。此外,C++ 标准保证在构造函数内部访问 this 指针。

解决这个问题的好方法:

struct node {
    int data;
    node& next;
    node(node& n, int d): next(n), data(d) {}
};

node tl(tl, 69); // tl is already in the scope!