假设我想创建一个不可修改的链表(即只能遍历它,一旦最初创建就不能添加或删除节点)。这可以通过以下方式轻松实现:
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;
}
答案 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)
没有。原因:
答案 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
我无法创建一个不易于构造的节点(聚合?)。
(添加任何类型的基本构造函数都会导致我无法理解的原因,包括gcc
和clang
)。
出于同样的奇怪原因,我无法将节点封装在“容器”对象中。
所以制作一个可以像这样构造的物体对我来说是不可能的:
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!