【问题标题】:How can references be valid while iterators become invalidated in a deque当迭代器在双端队列中失效时引用如何有效
【发布时间】:2015-12-24 08:18:32
【问题描述】:

我很难理解这个概念。从这个thread这里声明

双端队列要求任何对前面或后面的插入都应保持 对成员元素的任何引用都有效。迭代器可以 无效,但成员本身必须留在同一个地方 记忆。

我对@9​​87654322@线程的印象

指针实际上是一种迭代器。其实对于一些容器类型,对应的迭代器可以是 简单地实现为指针。

如果我们有一个指针和一个迭代器,它们都引用相同的 容器的元素,那么任何使容器无效的操作都会 使另一个无效。

所以如果迭代器失效,那么引用也会失效。 我的问题是这怎么可能。如果指向某个内存地址的迭代器失效,那么对该地址的引用如何有效?

更新:

我知道双端队列是由随机的内存块实现的,这些内存块由独立的数据结构(例如动态数组)跟踪。但是,我很难理解迭代器如何无效但引用可能有效,因为本质上迭代器是数据结构内容的通用指针。这让我觉得迭代器可能指向其他东西,而指针指向实际项目?考虑下面的向量图。

根据我在上图中对向量的理解,如果指针的内容发生变化,迭代器也会发生变化。 deque 有什么不同。

【问题讨论】:

  • 请注意,通常iterator 不是pointer 并且引用类型reference_type 不同,后者是容器在STL 中返回的。
  • 因为deque 通常是通过多个分配(“页面”)实现的,这些分配由额外的类似数组的数据结构管理。添加元素时可能需要重新分配该数据结构,但不必重新分配“页面”。
  • @dyp 你是说迭代器指向数据结构的内容而不是页面?
  • 您是否尝试过查看deque 的实现?也许是你的编译器附带的那个。也可以使用调试器跟踪一些操作。
  • 虽然迭代器必须引用它所指向的实际元素,但它还必须知道页面数组,因为它需要能够从一个页面跳转到另一个页面。所以它包含两条信息,例如两个指针,页面数组的信息可以在这里失效。

标签: c++ pointers iterator deque


【解决方案1】:

因为您引用的答案是错误,并且因为迭代器不仅仅是指针。首先,链表迭代器需要一个指向元素的指针,还需要“下一个”和“上一个”指针。就在那里,通过这个简单的例子,你认为“迭代器是数据结构内容的通用指针”的想法完全被吹掉了。

deque 比完全连续的结构(例如 vector)更复杂,并且比完全不连续的结构(例如 list)更复杂。当一个双端队列增长时,它的整体结构会适应,实际元素的重新分配最少(通常,none)。

结果是,即使某些元素不移动,允许访问它们的“控制部分”可能需要使用新的元数据进行更新,例如,相邻元素的位置(可能 确实 move) 现在是。

现在,deque 不能神奇地更新已经在某处实例化的迭代器:它所能做的就是记录您的旧迭代器无效,并且您应该以通常的方式获取新的迭代器。

【讨论】:

  • “首先,链表迭代器需要一个指向元素的指针,还需要“下一个”和“上一个”指针。” 这听起来有点像迭代器必须存储这些指针(事实并非如此)。
  • "实际元素的最小重新分配(通常,没有)"当它在末端增长时,它必须保证没有单个元素移动。否则不能依靠指针和引用来保持有效。列表迭代器可以是单个指针,指向一个结构,该结构同时包含一个元素和指向此类结构的下一个和上一个的指针。
  • @DanielJour:是的,我知道。这和我说的有什么矛盾?
  • 没有,如果我给人留下了这样的印象,请原谅。我只是认为可以强调这一点(因为特别要求在末尾进行修改)。
【解决方案2】:

从以下方面考虑双端队列:

template<typename T>
struct deque_stub {
 using Page = std::array<T, 32>; // Note: Not really, rather uninitialised memory of some size;
 std::vector<std::unique_ptr<Page>> pointers_to_pages;
 std::size_t end_insert{32};
 std::size_t start_elem{0};
 // read further
};

deque 基本上是一些容器,存储指向包含某些元素的页面的指针。 (start_elemend_insert 成员用于跟踪元素的有效范围开始和结束的位置,就页面偏移而言。)

当需要一个新页面时,插入最终会改变这个容器:

template<typename X>
void push_back(X&& element) {
 if (end_insert == 32) {
  // get a new page at the end
  pointers_to_pages.push_back(make_unique<Page>());
  end_insert = 0;
 }
 (*(pointers_to_pages.back()))[end_insert] = std::forward<X>(element);
 ++end_insert;
}

template<typename X>
void push_front(X&& element) {
 if (start_elem == 0) {
  pointers_to_pages.insert(
    pointers_to_pages.begin(), std::make_unique<Page>());
  start_elem = 32;
 }
 --start_elem;
 (*(pointers_to_pages.front()))[start_elem] = std::forward<X>(element);
}

进入该双端队列的迭代器需要能够跨页面“跳转”。实现这一点的最简单方法是让它从容器pointers_to_pages 中保持一个指向当前页面的迭代器:

struct iterator {
 std::size_t pos;
 std::vector<std::unique_ptr<Page>>::iterator page;
 // other members to detect page boundaries etc.
};

但是由于 page 迭代器,即向量中的迭代器,可能会在向量发生更改时失效(这发生在需要新页面时),所以插入元素时,整个双端队列中的迭代器可能会失效。 (这可以通过不使用向量作为指针的容器来“修复”,尽管这可能会产生其他负面影响。)


例如,考虑一个具有单个但完整页面的双端队列。因此,包含指向页面的指针的向量只包含一个元素,比如地址0x10,让我们进一步假设它的当前容量也只有1个元素。页面本身存储在某个地址,比如0x100

因此,双端队列的第一个元素实际上存储在0x100,但在双端队列中使用迭代器意味着首先查看0x10 以获取页面地址。

现在,如果我们在最后添加另一个元素,我们需要一个新页面来存储它。所以我们分配一个,并将指向该新页面的指针存储到向量中。由于它的容量小于新大小(1 &lt; 2),它需要分配一个新的更大的内存区域并将其当前内容移动到那里。假设,新区域位于0x20。先前存储指针的内存 (0x10) 被释放。

现在,插入之前上面的相同元素仍然位于相同的地址 (0x100),但它的迭代器将通过 0x20。上面的迭代器,访问0x10,因此无效。

由于元素位于相同的地址,指向它的指针和引用仍然有效,很难。

【讨论】:

    猜你喜欢
    • 2010-12-12
    • 2013-11-13
    • 2010-10-29
    • 1970-01-01
    • 1970-01-01
    • 2017-05-20
    • 2013-06-03
    • 2014-03-14
    • 2015-06-26
    相关资源
    最近更新 更多