简体   繁体   English

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

[英]Linked-list in C++ using references instead of pointers

Suppose I want to create an unmodifiable linked-list (ie it can only be traversed, no nodes can be added or removed once it was initially created).假设我想创建一个不可修改的链表(即它只能被遍历,一旦最初创建,就不能添加或删除节点)。 This could be easily implemented by:这可以通过以下方式轻松实现:

struct ListNode
{
  int value;
  ListNode* nextNode;
}

My question is .... Would it be possible to use references instead of pointers?我的问题是......是否可以使用引用而不是指针?

struct ListNodeWithRefs
{
  int value;
  ListNodeWithRefs &nextNode;
}

I am not sure it would provide any performance gain at all but ... this question popped up while coding and my answer so far is no but I could be missing something.我不确定它是否会提供任何性能提升,但是......编码时出现了这个问题,到目前为止我的答案是否定的,但我可能会遗漏一些东西。

In principle, nothing prevents you from using references, and constructing list elments like this:原则上,没有什么可以阻止您使用引用,并像这样构建列表元素:

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

But there is a chicken and egg problem because next also enforces its next element to exist at its creation and so on ...但是有一个先有鸡还是先有蛋的问题,因为next还会强制其next元素在其创建时存在,依此类推......

Note: I think my question can also be applied to defining the list as:注意:我认为我的问题也可以应用于将列表定义为:

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

Take a look at this example by sbi, it seems to work: https://stackoverflow.com/a/3003607/1758762看看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);
}

This is typical of a cons-list in functional languages:这是函数式语言中的典型缺点列表

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

The trick is though, List a is a full type and can refer either to Empty OR another node (which is why it can terminate).诀窍是,虽然, List a是一个完整的类型,可以指以Empty或其他节点(这就是为什么它可以终止)。

In order to achieve this in C++, you could take advantage of either a union (but it's not that well supported) or of polymorphism .为了在 C++ 中实现这一点,您可以利用联合(但它没有得到很好的支持)或多态性

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

    Kind _kind;
};

And then:接着:

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;
};

And now, you don't have a chicken & egg problem any longer;现在,您不再有鸡和蛋的问题了; just start from an instantiation of EmptyList<T> .只需从EmptyList<T>的实例化开始。

Note: the presence of _kind in the base class is not that OO, but it makes things closer to the functional example by tagging which alternative is used.注意:基类中_kind的存在不是面向对象,但它通过标记使用哪个替代方案使事情更接近功能示例。

How does the list end?名单如何结束?

You will need at least two types: the end and not.您至少需要两种类型:结束和不结束。 You also need lifetime management.您还需要终身管理。 And either runtime or static knowledge of which type.以及哪种类型的运行时或静态知识。

A completely static implementation could be done, where each node is its own type that knows how far it is to the end.可以完成一个完全静态的实现,其中每个节点都是它自己的类型,知道它到底有多远。

Or you could just have an unintialized buffer, and create elements off it in reverse order.或者你可以有一个未初始化的缓冲区,并以相反的顺序创建元素。

A circle is also possible.圆也是可能的。 Make the first reference refer to the last element you construct.使第一个引用引用您构造的最后一个元素。

No. Reasons:不。 原因:

  1. You cannot insert a node if nextNode is a reference.如果 nextNode 是引用,则不能插入节点。
  2. What should nextNode refer to if this is list tail?如果这是列表尾,nextNode 应该指什么?

As @Vlad said, the problem with references is that you will need a final object.正如@Vlad 所说,引用的问题在于您需要一个最终对象。 The good news is that, in principle, you can still have a cyclic list, if you have a use for it.好消息是,原则上,你仍然可以拥有一个循环列表,如果你有用的话。 This is a fundamental thing, if the "next" element is a non-nullable reference means that there is always a next element, that is, the list is either infinite or, more realistically, it closes on itself or into another list.这是一个基本的事情,如果“下一个”元素是一个不可为空的引用,则意味着总有一个下一个元素,也就是说,列表要么是无限的,要么更现实地说,它关闭自身或进入另一个列表。

Taken the exercise further is quite interesting and strange .进一步练习是相当有趣和奇怪的 Basically the only thing that seems to be possible is to defined the equivalent of the a node (which also represents the list).基本上唯一似乎可能的是定义 a 节点的等价物(它也代表列表)。

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};}
};

The elements have to live in the stack and the list is static (well, references are not rebindable anyway) and the links have to be const references!元素必须存在于堆栈中并且列表是静态的(好吧,无论如何引用都不能重新绑定)并且链接必须是const引用! Eventually the value can be made then mutable apparently (probably safe?).最终,该value可以变得明显mutable (可能是安全的?)。 (At this point one wonders how is this different from a stack array references by a modulo indices.) (此时人们想知道这与模索引的堆栈数组引用有何不同。)

There is only one way to construct the node /list object, that is to close it with itself (or with other preexising node).只有一种方法可以构造node /list 对象,即用自身(或其他预先存在的节点)关闭它。 So the resulting list are either circular or "rho" shape.所以结果列表要么是圆形,要么是“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

I wasn't able to make a node that is not trivially constructible (aggregate?).我无法创建一个不可简单构建的节点(聚合?)。 (Adding any sort of basic constructor produced segmentation faults for a reason I can't understand, both in gcc and clang ). (由于我无法理解的原因,在gccclang添加任何类型的基本构造函数都会产生分段错误)。 I wasn't able to encapsulate the node in a "container" object for the same strange reason.由于同样的奇怪原因,我无法将节点封装在“容器”对象中。 So making an object that could be constructed like this was impossible to me:所以制作一个可以像这样构造的对象对我来说是不可能的:

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

Finally, since a proper container cannot be constructed it is not clear what is the semantics of this object, for example when two "lists" are equal?最后,由于无法构建合适的容器,因此不清楚这个对象的语义是什么,例如当两个“列表”相等时? what doesn't mean to assign?什么不是赋值? or assign between list of different sizes?或在不同大小的列表之间分配?

It is a quite paradoxical object, with no general value or reference semantics apparently.它是一个相当矛盾的对象,显然没有一般价值或参考语义。

Any comments or improvements are welcomed!欢迎任何意见或改进!

I might be off the mark, but this works我可能不合时宜,但这有效

    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;
};

The usual tests常规测试

 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;

Of course, as long as all Node's stay in the scope we are all ok here.当然,只要所有的Node都在作用域内,我们这里就OK了。 Otherwise not.否则不行。 Strange and wonderful.奇怪而美妙。 Very limited utility?实用性非常有限?

That was quite tricky but this worked :这很棘手,但这有效:

#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();
}

A simple way to avoid a chicken-egg problem for a list with references is to remember that firstly your object memory is allocated, then your constructor is called.避免带有引用的列表出现先天问题的一个简单方法是记住,首先分配对象内存,然后调用构造函数。 Moreover, access to this pointer is guaranteed inside of a constructor by C++ standard.此外,C++ 标准保证在构造函数内部访问this指针。

Neat way to resolve 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!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM