TL; DR:我有一个链接的数据结构,我决定不使用指针,而是使用一个指向容器的索引来表示这些链接。为了使代码更具可读性,是否可以将单个元素建模为独立对象,而又不会产生保持对数组的多个引用的成本?
假设我有一个链接的数据结构。为了简单起见,我们以一个双向链表为例,并带有删除节点的操作。建模的经典方法是使用指针:
struct Node {
Node *prev, *next;
void remove() { next->prev = prev; prev->next = next; }
};
但是指针有很多缺点。由于通常无法选择指针大小来匹配用例,因此它们可能会浪费空间。它们导致较差的电汇格式。如果将节点保留在向量中,则调整大小可能会使指针无效。复制数据结构变得更加困难。所以我可以改为将索引放入某个数组中:
struct Node {
int prev, next;
};
struct LinkedList {
std::vector<Node> nodes;
void remove(int i) {
Node& n = nodes[i];
nodes[n.next].prev = n.prev;
nodes[n.prev].next = n.next;
}
};
但是现在,以前是单个节点的方法的操作已成为容器类的方法。这种语义上的转换使某些代码更难阅读。为避免该问题,我可以根据容器和节点索引对进行表示。
struct Node { int prev, next; };
struct LinkedList;
struct NodeRef {
int i;
LinkedList& l; // This reference here is what's worrying me
NodeRef(int index, LinkedList& list) : i(index), l(list) {}
NodeRef prev() const;
NodeRef next() const;
void remove();
};
struct LinkedList {
std::vector<Node> nodes;
NodeRef root() { return NodeRef(0, *this); }
};
NodeRef NodeRef::prev() const { return NodeRef(l.nodes[i].prev, l); }
NodeRef NodeRef::next() const { return NodeRef(l.nodes[i].next, l); }
void NodeRef::remove() {
Node& n = l.nodes[i];
l.nodes[n.next].prev = n.prev;
l.nodes[n.prev].next = n.next;
}
因此,现在我可以使用NodeRef来以一种很好的面向对象的方式来表达我的算法,将节点作为一个可以迭代的实体,同时使用索引代替幕后的指针。
但是当我有一些复杂的算法同时在多个节点上运行时,那么我将有多个NodeRef对象都引用同一个基础LinkedList对象。无论是在内存消耗方面还是在进行复制方面,这都是一种浪费。我会猜测,认为编译器可能能够检测到某些冗余并将其消除。但是,有什么我可以做的,以确保即使在语义上我有多个引用也可以优化为仅使用单个引用?
答案 0 :(得分:1)
当您想对LinkedList
进行操作时,可能需要提供NodeRef
,然后有一个访问对象或一个lambda来存储您的单个引用并包装对{{1}的调用}。
例如,从列表中删除节点,您可以使用:
NodeRef
不确定这是否是您想要的,但是它避免了对该列表的多次引用,并且可能允许重复使用struct NodeRef
{
int i;
// LinkedList& l; // Remove this
NodeRef(int index) : i(index) {}
NodeRef prev(LinkedList& l) const;
NodeRef next(LinkedList& l) const;
void remove(LinkedList& l);
};
// Require that a list is supplied instead of storing a ref
void NodeRef::remove(LinkedList& l)
{
Node& n = l.nodes[i];
l.nodes[n.next].prev = n.prev;
l.nodes[n.prev].next = n.next;
}
// create a lambda with a captured list that wraps the call
LinkedList linkedList;
//...
auto remover = [&linkedList](NodeRef node)
{
node.remove(linkedList);
};
//...
NodeRef nodeRef(0);
remover(nodeRef);
对象。
答案 1 :(得分:0)
另一种选择是使用模板存储指向列表的静态指针,该指针在所有派生类之间共享。使用不同的int来区分您的列表和NodeRef
。您不得不在编译时再次管理这些int,这可能并不理想,但这确实解决了多引用问题。
#include <vector>
template <int X>
struct LinkedList;
template <int X>
struct NodeRef
{
static LinkedList<X>* l;
int i;
NodeRef(int index) : i(index) {}
NodeRef prev() const;
NodeRef next() const;
void remove();
};
struct Node { int prev, next; };
template <int X>
struct LinkedList
{
LinkedList()
{
NodeRef<X>::l = this;
}
std::vector<Node> nodes;
NodeRef<X> root() { return NodeRef<X>(0); }
};
// Use the static pointer
template<int X>
void NodeRef<X>::remove()
{
auto& l = *NodeRef<X>::l;
Node& n = l.nodes[i];
l.nodes[n.next].prev = n.prev;
l.nodes[n.prev].next = n.next;
}
int main()
{
LinkedList<0> linkedList;
// All node refs templated by 0 share the list pointer
auto nodeRef = linkedList.root();
}
答案 2 :(得分:0)
您可以使所有LinkedList
共享一个Node
个池:
struct LinkedList {
static std::vector<Node> nodes; // <-- static
void remove(int i) {
Node& n = nodes[i];
nodes[n.next].prev = n.prev;
nodes[n.prev].next = n.next;
}
};
这样,对于NodeRef
而言,仅索引就足以识别引用的Node
。
但是请注意,根据LinkedList
和Node
的创建,修改和销毁的频率,您可能会由于缓存未命中而遇到性能问题,因为Node
列表中的邻居可能根本不是内存中的邻居。
您当前的方法可能已经存在此问题。在这种情况下,全局Node
池会使情况变得更糟。
此外,如果您有多线程应用程序,则必须执行通常的锁定机制,这将进一步降低应用程序的速度。