【发布时间】:2019-03-27 23:33:42
【问题描述】:
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 对象。就内存消耗和复制它们的工作而言,这感觉有点浪费。我猜测编译器可能能够检测到一些冗余并摆脱它。但是我能做些什么来帮助它,以确保即使在语义上我有多个引用,它也会被优化为只使用一个引用?
【问题讨论】:
-
您是否考虑过使用
operator[]重载来抽象出交互和连接的智能指针?智能指针也可以处理多个 NodeRef 对象的问题。 -
@Tzalumen:当没有明确的所有权时,智能指针天生就很棘手,大多数不是树的链接数据结构就是这种情况。智能指针的主要好处是不错的基于 RAII 的清理,但清理甚至不是我列出的指针的问题之一,所以我列出的所有问题仍然适用于智能指针。不知道你会如何在这里使用
operator[]。在LinkedList上定义它以构造NodeRef可以使抽象代码更易于阅读,但不会影响手头的问题,因为冗余引用仍然存在。 -
你看过
std::weak_ptr和std::shared_from_this吗?weak_ptr用于循环中的最终指针,shared_from_this为您提供了一种简单的方法来通过多个NodeRef引用相同的LinkedList来强制共享所有权。 -
另外一个问题,您是否有理由不只是使用
std::list而不是自己滚动?
标签: c++ oop data-structures idioms