【问题标题】:Linked-list in C++ using references instead of pointersC++ 中的链表使用引用而不是指针
【发布时间】:2013-02-17 15:13:27
【问题描述】:

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

struct ListNode
{
  int value;
  ListNode* nextNode;
}

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

struct ListNodeWithRefs
{
  int value;
  ListNodeWithRefs &nextNode;
}

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

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

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

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

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

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

【问题讨论】:

  • 只有尝试在列表头部旁边的任何位置插入节点时才会出现问题。
  • 你将如何表示最终节点?引用不能为 NULL。
  • 正如@Vlad 所说,引用的问题是您需要一个最终对象。好消息是,原则上,您仍然可以拥有一个循环列表。如果你有它的用途。

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


【解决方案1】:

这是函数式语言中典型的cons-list

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

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

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

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&lt;T&gt; 的实例化开始。

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

【讨论】:

  • 或者只是boost::variant&lt;ListBase&lt;T&gt;, EmptyList&gt; NextNode,加上strust ListNode&lt;T&gt;{ T Value; NextNode next; };——这不是逐字代码,因为你需要使用递归variant语法来让事情正常工作。 EmptyList 不必与 ListBase&lt;T&gt; 相关。作为另一种选择,boost::optional&lt;ListNode&lt;T&gt;&gt; 可能更接近这个概念(为什么标签为空,当你不能拥有它时?)。
  • @Yakk: 是的,boost::optional&lt;T&gt; 是 Haskell 的 Maybe a 的直接映射,boost::variant&lt;T,U&gt; 是 Haskell 的 Either a b 的直接映射,实际上,两者都允许摆脱继承。我认为它们也是稍微复杂一些的概念。
【解决方案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);
}

【讨论】:

  • 你能举个例子来举例说明如何使用这个类吗?看起来必须一个一个地生成节点(处于某种无效状态),然后一个一个地链接它们。
  • 我想我更了解这个解决方案。但它所做的技巧基本上是通过放置新的技巧重新绑定引用的技巧。我不知道它是否是 UB,但它违背了使用引用作为链接的目的。它基本上将它们用作(可重新绑定的)指针。
【解决方案3】:

列表如何结束?

您至少需要两种类型:end 和 not。您还需要生命周期管理。以及哪种类型的运行时或静态知识。

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

或者你可以只拥有一个未初始化的缓冲区,然后以相反的顺序从它上面创建元素。

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

【讨论】:

    【解决方案4】:

    没有。原因:

    1. 如果 nextNode 是引用,则不能插入节点。
    2. 如果这是列表尾,nextNode 应该指代什么?

    【讨论】:

    • 错误:1/问题是关于一个不可修改的列表,2/你可以让最后一个元素来自超类,而没有 nextnode 引用。
    【解决方案5】:

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

    进一步练习非常有趣且奇怪。 基本上,唯一似乎可能的事情是定义 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};}
    };
    

    元素必须存在于堆栈中并且列表是静态的(好吧,无论如何引用都不能重新绑定)并且链接必须是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
    

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

    这是一个相当矛盾的对象,显然没有普遍的价值或引用语义。

    欢迎任何 cmets 或改进!

    【讨论】:

      【解决方案6】:

      我可能会跑题,但这行得通

          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 都在范围内,我们在这里都可以。否则不行。奇怪而美妙。实用性非常有限?

      【讨论】:

        【解决方案7】:

        这很棘手,但这很有效:

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

        【讨论】:

          【解决方案8】:

          避免对带有引用的列表产生鸡蛋问题的简单方法是记住首先分配对象内存,然后调用构造函数。此外,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!
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-02-09
            • 1970-01-01
            • 2020-11-27
            • 2015-09-09
            • 2012-08-25
            • 2021-06-29
            • 2016-08-08
            • 2012-04-20
            相关资源
            最近更新 更多