tl;dr :是的,移动 astd::vector<T, A>
可能会使迭代器无效
常见的情况(std::allocator
就地)是无效不会发生,但不能保证,如果您依赖于您的实现当前不会使迭代器无效的事实,切换编译器甚至下一次编译器更新可能会使您的代码行为不正确。
移动任务:
在移动赋值之后迭代器是否std::vector
实际上保持有效的问题与向量模板的分配器意识有关,并且取决于分配器类型(可能还有其各自的实例)。
在我见过的每个实现中,移动赋值std::vector<T, std::allocator<T>>
1实际上不会使迭代器或指针无效。然而,当涉及到使用它时存在一个问题,因为标准不能保证迭代器对于std::vector
实例的任何移动分配通常保持有效,因为容器是分配器感知的。
自定义分配器可能具有状态,如果它们在移动分配时不传播并且不比较相等,则向量必须使用其自己的分配器为移动的元素分配存储空间。
让:
std::vector<T, A> a{/*...*/};
std::vector<T, A> b;
b = std::move(a);
现在如果
std::allocator_traits<A>::propagate_on_container_move_assignment::value == false &&
std::allocator_traits<A>::is_always_equal::value == false &&
(可能从 c++17 开始)
a.get_allocator() != b.get_allocator()
然后b
将分配新的存储空间并将元素a
一个接一个地移动到该存储空间中,从而使所有迭代器、指针和引用无效。
原因是满足上述条件1.禁止在容器移动时分配器的移动分配。因此,我们必须处理分配器的两个不同实例。如果这两个分配器对象现在既不总是比较相等(2.)也不实际比较相等,那么两个分配器具有不同的状态。分配器x
可能无法释放另一个y
具有不同状态的分配器的内存,因此具有分配器的容器x
不能仅仅从通过 分配其内存的容器中窃取内存y
。
如果分配器在移动分配上传播,或者如果两个分配器比较相等,那么实现很可能会选择只生成b
自己a
的数据,因为它可以确保能够正确地解除分配存储。
1 :std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignment
并且std::allocator_traits<std::allocator<T>>::is_always_equal
两者都是std::true_type
(对于任何非专业的std::allocator
)的 typdefs。
移动施工:
std::vector<T, A> a{/*...*/};
std::vector<T, A> b(std::move(a));
分配器感知容器的移动构造函数将从当前表达式从中移动的容器的分配器实例中移动构造其分配器实例。因此,确保了适当的释放能力,并且内存可以(实际上将)被盗,因为移动构造(除了std::array
)必然具有恒定的复杂性。
注意:即使对于移动构造,仍然不能保证迭代器保持有效。
交换时:
要求两个向量的迭代器在交换后保持有效(现在只是指向相应的交换容器)很容易,因为交换只有在以下情况下才具有定义的行为
std::allocator_traits<A>::propagate_on_container_swap::value == true ||
a.get_allocator() == b.get_allocator()
因此,如果分配器不在交换时传播,并且如果它们不比较相等,那么交换容器首先是未定义的行为。