移动vector是否使迭代器无效?

如果我有一个向量的迭代器,那么我移动构造或移动 – 从该向量分配另一个向量,该迭代器是否仍然指向新向量中的有效元素? 这是一个简单的例子:

#include <vector> #include <iostream> int main(int argc, char *argv[]) { std::vector<int>::iterator a_iter; std::vector<int> b; { std::vector<int> a{1, 2, 3, 4, 5}; a_iter = a.begin() + 2; b = std::move(a); } std::cout << *a_iter << std::endl; return 0; } 

a_iter仍然是有效的,因为a被移入b ,或者被移动无效的迭代器? 作为参考, std::vector::swap 不会使迭代器失效 。

尽pipe假设iteratormove后依然有效,但我认为标准并不能保证这一点。 因此, move后迭代器处于未定义状态。


在标准中没有我能find的参考资料,它明确指出 move之前存在的迭代器在move 之后仍然有效。

从表面上看,假定iterator 通常被实现为指向受控序列的指针似乎是完全合理的。 如果是这样的话,迭代器在move后仍然有效。

但是iterator的实现是实现定义的。 也就是说,只要特定平台上的iterator满足标准规定的要求,就可以以任何方式实现。 理论上讲,它可以作为一个指针和一个索引的组合来实现。 如果是这样的话,迭代器在move之后将会失效。

iterator是否实际上以这种方式实现是无关紧要的。 它可以用这种方式实现,所以如果没有标准的特定保证, move迭代器仍然有效,那么你不能假设它们是。 还要记住,在swap之后迭代器有这样的保证。 这是从以前的标准具体澄清。 或许这只是对Std委员会的一个疏忽,不要在迭代之后对迭代器做类似的澄清,但是在任何情况下都没有这样的保证。

所以,长短不一,你不能假设你的迭代器在move后仍然是好的。

编辑:

草案n3242中的23.2.1 / 11指出:

除非另有规定(明确地或通过用其他函数定义函数),调用容器成员函数或将容器作为parameter passing给库函数不应使迭代器失效或更改该容器内的对象的值。

这可能会导致一个人得出这样的结论:迭代器在move后是有效的,但我不同意。 在你的示例代码中, a_itervector a的迭代器。 move ,那个集装箱,一定是变了。 我的结论是上述条款不适用于这种情况。

我认为改动了移动build筑的编辑转移答案。

至less如果我正确地阅读了表格96,移动构造的复杂性被赋予为“note B”,除了std::array之外的任何东西都是不变的复杂性。 然而,移动分配的复杂性是线性的。

因此,移动构造本质上是别无select,只能从源头复制指针,在这种情况下很难看出迭代器如何失效。

然而,对于移动赋值,线性复杂度意味着它可以select将各个元素从源移动到目标,在这种情况下,迭代器几乎肯定会失效。

元素的移动分配的可能性通过描述得到了加强:“a的所有现有元素要么被分配到要么被销毁”。 “销毁”部分将对应于销毁现有内容,并且从指示源“窃取”指示器 – 但是“指派给的移动”将指示将来自源的目标移动到目的地。

既然没有什么可以让迭代器保留一个引用或指向原始容器的指针,我会说你不能依靠迭代器保持有效,除非你在标准中find明确的保证。

tl; dr:是的,移动std::vector<T, A>可能使迭代器失效

常见的情况(使用std::allocator )是失效不会发生,但是没有任何保证,如果您依赖于您的实现目前没有,切换编译器甚至下一次编译器更新可能会使您的代码行为不正确使迭代器无效。


在移动任务

std::vector迭代器在移动赋值之后是否实际上仍然有效的问题与vector模板的分配器意识相关联,并且取决于分配器types(以及可能的相应实例)。

在我看到的每个实现中, std::vector<T, std::allocator<T>> 1的移动赋值实际上不会使迭代器或指针无效。 有一个问题,但是,当涉及到使用这个,因为标准只是不能保证迭代器保持有效的任何移动分配一般的std::vector实例,因为容器是分配器感知。

自定义分配器可能有状态,如果它们不在移动赋值上传播,也不会比较相等,则向量必须使用自己的分配器为移动的元素分配存储空间。

让:

 std::vector<T, A> a{/*...*/}; std::vector<T, A> b; b = std::move(a); 

现在如果

  1. std::allocator_traits<A>::propagate_on_container_move_assignment::value == false &&
  2. std::allocator_traits<A>::is_always_equal::value == false &&可能从c ++ 17开始
  3. a.get_allocator() != b.get_allocator()

那么b将分配新的存储空间并将一个接一个的元素移入该存储区,从而使所有迭代器,指针和引用无效。

原因是满足上述条件1.禁止移动分配器的任务。 因此,我们必须处理分配器的两个不同的实例。 如果这两个分配器对象现在既不总是比较相等( 2. )也不实际比较相等,那么两个分配器都有不同的状态。 分配器x可能不能解除分配另一个分配器y内存的状态,因此具有分配器x的容器不能从通过y分配内存的容器中窃取内存。

如果分配器在移动赋值时传播,或者如果两个分配器比较相等,那么实现将很可能select只拥有a s数据,因为它可以确保能够正确地释放存储。

1std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignmentstd::allocator_traits<std::allocator<T>>::is_always_equal两者都是std::true_type (对于任何非专用的std::allocator )。


在施工中

 std::vector<T, A> a{/*...*/}; std::vector<T, A> b(std::move(a)); 

分配器感知容器的移动构造器将从当前expression式所从容器的分配器实例移动构build其分配器实例。 因此,确保正确的释放能力,并且内存可以(并且实际上将被)被盗,因为移动构造( std::array除外)必然具有固定的复杂性。

注意:即使移动构build,迭代器仍然不能保持有效。


交换时

交换之后要求两个向量的迭代器保持有效(现在只需指向相应的交换容器)是很容易的,因为交换只定义了行为if

  1. std::allocator_traits<A>::propagate_on_container_swap::value == true ||
  2. a.get_allocator() == b.get_allocator()

因此,如果分配器不在swap上传播,并且如果它们不相等,则交换容器首先是未定义的行为。