std::vector 如何访问它们之间存在巨大差距的元素?

Posted

技术标签:

【中文标题】std::vector 如何访问它们之间存在巨大差距的元素?【英文标题】:How can std::vector access elements with huge gaps between them? 【发布时间】:2020-11-21 13:36:43 【问题描述】:

拥有此代码:

template <class IIt, class OIt>
OIt copy2(IIt begin, IIt end, OIt dest)

   while (begin != end)
   
      //make gap between element addresses
      for (int i = 0; i < 99999999; i++)
      
         dest++;
      
      *dest++ = *begin++;
   
   return dest;


int main(int argc, char** argv)

   vector<int> vec( 1, 2, 3 );
   vector<int> vec2;
   copy2(vec.begin(), vec.end(), back_inserter(vec2));
   for (int i : vec2)
   
      cout << i << endl;
   

编译需要很长时间,但最终会得到正确的输出

1
2
3

问题是(不知道std::vector的内部实现,是c-style数组?还是更复杂的结构?),如何正确找到 for(int i:vec2) 中的那些元素,当这些元素的地址(指针)不是连续时? (即因为迭代器/指针移动了99999999)。

我认为OutputIterator 需要拥有该属性,即只能对其执行一次访问、一次轮班。但是,当您在访问它们之间多次移动(添加)它时,就会出现一个差距,这在我的情况下是相当大的。那么它是如何编译的呢?

【问题讨论】:

@Graham 向量为您提供了一个指向 data() 的 C 样式数组的指针,但它可以在不影响现有元素的情况下调整其大小(最大容量)。在 C++20 之前,这需要实现定义的魔法 @Caleth 允许调整它的大小,这在实践中意味着在需要更多分配之前分配具有空间的连续更大的数组以增长到某个限制。因为底层存储被定义为一个连续的 C 样式数组,调整大小可能需要新的分配和复制,因此您不能依赖指针在调整大小后保持有效。 @Graham 我不是说重新分配,但是容量 >= 5 的 vector&lt;T&gt; 可以分发指向 T[4] 的指针,插入一个元素,然后分发一个指向T[5]T[4] 具有相同的地址。它必须以某种方式破坏T[4] 并构造T[5] 而不会影响元素 @Caleth 插入后,T[5]的地址理论上可以和前面的T[4]相同,具体取决于实现;但它也可能完全是记忆的另一端。任何调整大小操作后,指向内部存储的指针都将失效,因此在插入之后,您需要找到该 T[5] 元素的新地址。这就是为什么所有调整大小操作都会产生使所有迭代器无效的副作用。您可以保证前一个 T[4] 中的 data 在插入后会在 T[5] 中,但不是该数据在内存中的位置。 我认为您将“编译”与“执行”混淆了。 【参考方案1】:

你被骗了。

std::back_inserter 返回的迭代器有it++ as a no-op。那么你正在创造的那些“差距”?是的,这一切都无济于事。

【讨论】:

但很明显,OP 知道没有间隙,他们确实打印了矢量......真的不清楚他们认为他们在用这个循环做什么。 @Jeffrey 我对他们的前提的解释是,间隙在某处,并且在某处有一个指针,指针跳过间隙,指针不应该这样做。 他们还有一个问题,如果它确实重新分配数组以创建一个间隙,那么他们所有的指针和迭代器都会变得无效,他们的代码会抛出异常。所以它只能通过不按预期工作来运行。 :)【参考方案2】:

std::vector,是c风格的数组吗?

不完全是,但它创建的缓冲区在结构上是相同的。

那些元素的地址(指针)不是连续的?

前提是错误的。向量元素的内存地址是连续的。一个对象紧接着另一个对象开始。

此外,它们是否是连续的并不重要。即使这些元素在内存中不连续,您也可以很好地迭代链表。

OutputIterator ...但是当您在访问它们之间多次移动(添加)它时,就会出现差距

这个假设是不正确的。

std::back_insert_iterator 的特殊情况下,文档说:

std::back_insert_iterator<Container>::operator++

什么都不做。

【讨论】:

【参考方案3】:

你的 for 循环

for (int i = 0; i < 99999999; i++)

    dest++;

没有按照你的想法做。除了从0 迭代到99999999 之外,它对那里没有任何影响。

当您查看 std::back_insert_iterator 时,它会说

[...]。 增加std::back_insert_iterator 是无操作的。

或如23.5.2.1.1 中所述,它只是返回back_insert_iterator,而不对其进行任何操作。

constexpr back_insert_iterator& operator++();
constexpr back_insert_iterator  operator++(int);

#Returns: *this.

表示dest++; 无效。这使您所做的全部假设完全无效。程序执行的时间很长,只是因为从099999999 的迭代。


它提出了一个问题:那么为什么会有std::back_insert_iterator&lt;Container&gt;::operator++ 过载呢?

来自 cpprefereence std::back_insert_iterator&lt;Container&gt;::operator++:

什么都不做。提供这些运算符重载 是为了满足 的要求 LegacyOutputIterator。 它们使表达式*iter++=value*++iter=value 用于将值输出(插入)到 底层容器。

【讨论】:

以上是关于std::vector 如何访问它们之间存在巨大差距的元素?的主要内容,如果未能解决你的问题,请参考以下文章

大型数组、std::vector 和堆栈溢出

如何访问 std::vector 类型的成员

std::vector 的不完整类型

在 C++ std::vector 和 C 数组之间转换而不复制

C++ std::vector 乘法中是不是存在已知的不一致行为?

如何检查 std::vector 超出范围的访问