简体   繁体   English

移动向量会使迭代器无效吗?

[英]Does moving a vector invalidate iterators?

If I have an iterator into vector a , then I move-construct or move-assign vector b from a , does that iterator still point to the same element (now in vector b )? 如果我在向量a有一个迭代器,那么我从a移动构造或移动分配向量b ,那么该迭代器是否仍指向同一元素(现在在向量b )? Here's what I mean in code: 这就是我在代码中的意思:

#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; // Is a_iter valid here?
    return 0;
}

Is a_iter still valid since a has been moved into b , or is the iterator invalidated by the move? 由于a已移至ba_iter仍然有效,还是该迭代a_iter迭代器无效? For reference, std::vector::swap does not invalidate iterators . 作为参考, std::vector::swap 不会使迭代器无效

While it might be reasonable to assume that iterator s are still valid after a move , I don't think the Standard actually guarantees this. 尽管可以合理地假设在move之后iterator仍然有效,但我认为Standard并不能真正保证这一点。 Therefore, the iterators are in an undefined state after the move . 因此,在move之后,迭代器处于未定义状态。


There is no reference I can find in the Standard which specifically states that iterators that existed before a move are still valid after the move . 没有引用我可以在其中明确指出 ,之前已经存在迭代器的标准找到move仍然是有效的 move

On the surface, it would seem to be perfectly reasonable to assume that an iterator is typically implemented as pointers in to the controlled sequence. 从表面上看,假设iterator 通常被实现为指向受控序列的指针,这似乎是完全合理的。 If that's the case, then the iterators would still be valid after the move . 如果是这种情况,那么在move之后,迭代器仍然有效。

But the implementation of an iterator is implementation-defined. 但是iterator的实现是实现定义的。 Meaning, so long as the iterator on a particular platform meets the requirements set forth by the Standard, it can be implemented in any way whatsoever. 意思是,只要特定平台上的iterator满足标准规定的要求,就可以以任何方式实施。 It could, in theory, be implemented as a combination of a pointer back to the vector class along with an index. 从理论上讲,它可以实现为返回vector类的指针和索引的组合。 If that's the case, then the iterators would become invalid after the move . 如果真是这样,那么在move之后,迭代器将变得无效。

Whether or not an iterator is actually implemented this way is irrelevant. iterator是否实际以这种方式实现是无关紧要的。 It could be implemented this way, so without a specific guarantee from the Standard that post- move iterators are still valid, you cannot assume that they are. 可以通过这种方式来实现,因此,如果标准中没有明确保证move后迭代器仍然有效,则不能假设它们是有效的。 Bear in mind also that there is such a guarantee for iterators after a swap . 还请记住, swap之后,迭代器具有这样的保证。 This was specifically clarified from the previous Standard. 这是从先前的标准中明确阐明的。 Perhaps it was simply an oversight of the Std comittee to not make a similar clarification for iterators after a move , but in any case there is no such guarantee. 也许这只不过是标准comittee不要做出类似的澄清后,迭代器的监督move ,但在任何情况下,有没有这样的保证。

Therefore, the long and the short of it is you can't assume your iterators are still good after a move . 因此,总而言之,你不能认为move之后迭代器仍然是好的。

EDIT: 编辑:

23.2.1/11 in Draft n3242 states that: 第n3242号草案中的23.2.1 / 11指出:

Unless otherwise specified (either explicitly or by defining a function in terms of other functions), invoking a container member function or passing a container as an argument to a library function shall not invalidate iterators to, or change the values of, objects within that container. 除非另有说明(显式指定或通过在其他函数中定义一个函数),否则调用容器成员函数或将容器作为参数传递给库函数均不得使对该容器内对象的迭代器或更改其值无效。

This might lead one to conclude that the iterators are valid after a move , but I disagree. 这可能导致人们得出以下结论:迭代器在move之后是有效的,但我不同意。 In your example code, a_iter was an iterator in to the vector a . 在您的示例代码中, a_itervector a的迭代器。 After the move , that container, a has certainly been changed. 在之后move ,该容器, a肯定已经改变了。 My conclusion is the above clause does not apply in this case. 我的结论是以上条款不适用于这种情况。

I think the edit that changed move construction to move assignment changes the answer. 我认为将移动构造更改为移动分配的编辑会更改答案。

At least if I'm reading table 96 correctly, the complexity for move construction is given as "note B", which is constant complexity for anything except std::array . 至少如果我正确地读取了表96,则移动构造的复杂性将以“ note B”的形式给出,这对于除std::array以外的任何事物都是恒定的。 The complexity for move assignment , however, is given as linear. 但是,移动分配的复杂度是线性的。

As such, the move construction has essentially no choice but to copy the pointer from the source, in which case it's hard to see how the iterators could become invalid. 这样,移动构造基本上别无选择,只能从源复制指针,在这种情况下,很难看到迭代器如何变得无效。

For move assignment, however, the linear complexity means it could choose to move individual elements from the source to the destination, in which case the iterators will almost certainly become invalid. 但是,对于移动分配,线性复杂度意味着它可以选择将单个元素从源移动到目的地,在这种情况下,迭代器几乎肯定会变得无效。

The possibility of move assignment of elements is reinforced by the description: "All existing elements of a are either move assigned to or destroyed". 元素移动分配的可能性通过以下描述得到了增强:“ a的所有现有元素都被分配或销毁了”。 The "destroyed" part would correspond to destroying the existing contents, and "stealing" the pointer from the source -- but the "move assigned to" would indicate moving individual elements from source to destination instead. “销毁”部分将对应于销毁现有内容,并从源头“窃取”指针,但是“移动到”将指示将各个元素从源头移到目的地。

由于没有什么可以阻止迭代器保留对原始容器的引用或指针,因此除非您在标准中找到明确的保证,否则您不能依靠迭代器保持有效。

tl;dr : Yes, moving a std::vector<T, A> possibly invalidates the iterators tl; dr:是的,移动std::vector<T, A>可能会使迭代器无效

The common case (with std::allocator in place) is that invalidation does not happen but there is no guarantee and switching compilers or even the next compiler update might make your code behave incorrect if you rely on the fact the your implementation currently does not invalidate the iterators. 常见情况(使用std::allocator的情况)是不会发生无效,但不能保证,如果您依赖于当前实现未实现的事实,则切换编译器甚至是下一个编译器更新可能会使您的代码行为不正确。使迭代器无效。


On move assignment : 移动作业

The question whether std::vector iterators can actually remain valid after move-assignment is connected with the allocator awareness of the vector template and depends on the allocator type (and possibly the respective instances thereof). 在将移动分配与向量模板的分配器意识相关联之后, std::vector迭代器是否可以实际上保持有效的问题取决于分配器类型(可能还有其各自的实例)。

In every implementation I have seen, move-assignment of a std::vector<T, std::allocator<T>> 1 will not actually invalidate iterators or pointers. 在我所看到的每个实现中, std::vector<T, std::allocator<T>> 1的移动分配实际上不会使迭代器或指针无效。 There is a problem however, when it comes down to making use of this, as the standard just cannot guarantee that iterators remain valid for any move-assignment of a std::vector instance in general, because the container is allocator aware. 但是,有一个问题,当归结为使用它时,因为该标准通常不能保证迭代器对于std::vector实例的任何移动分配通常都是有效的,因为该容器可以识别分配器。

Custom allocators may have state and if they do not propagate on move assignment and do not compare equal, the vector must allocate storage for the moved elements using its own allocator. 自定义分配器可能具有状态,并且如果它们在移动分配时不传播并且比较不相等,则向量必须使用其自己的分配器为移动的元素分配存储。

Let: 让:

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

Now if 现在如果

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

then b will allocate new storage and move elements of a one by one into that storage, thus invalidating all iterators, pointers and references. 然后b将分配新的存储并将a元素一一移动到该存储中,从而使所有迭代器,指针和引用无效。

The reason is that fulfillment of above condition 1. forbidds move assignment of the allocator on container move. 原因是满足以上条件1.禁止在容器移动时分配分配器的移动。 Therefore, we have to deal with two different instances of the allocator. 因此,我们必须处理两个不同的分配器实例。 If those two allocator objects now neither always compare equal ( 2. ) nor actually compare equal, then both allocators have a different state. 如果这两个分配器对象现在既不总是比较等于( 2 ),也不实际等于相等,则两个分配器的状态都不同。 An allocator x may not be able to deallocate memory of another allocator y having a different state and therefore a container with allocator x cannot just steal memory from a container which allocated its memory via y . 分配器x可能无法解除分配状态不同的另一个分配器y内存,因此具有分配器x的容器不能仅从通过y分配了其内存的容器中窃取内存。

If the allocator propagates on move assignment or if both allocators compare equal, then an implementation will very likely choose to just make b own a s data because it can be sure to be able to deallocate the storage properly. 如果移动分配的分配器传播或者如果两个分配器比较相等,那么实施将很有可能选择只让b自己a s的数据,因为它可以确保能够正确地解除分配存储。

1 : std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignment and std::allocator_traits<std::allocator<T>>::is_always_equal both are typdefs for std::true_type (for any non-specialized std::allocator ). 1std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignment std::allocator_traits<std::allocator<T>>::is_always_equal std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignmentstd::allocator_traits<std::allocator<T>>::is_always_equal都是std::true_type (对于任何非专用std::allocator )。


On move construction : 在施工中

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

The move constructor of an allocator aware container will move-construct its allocator instance from the allocator instance of the container which the current expression is moving from. 知道分配器的容器的move构造函数将从当前表达式要从其移动的容器的分配器实例移动构造其分配器实例。 Thus, the proper deallocation capability is ensured and the memory can (and in fact will) be stolen because move construction is (except for std::array ) bound to have constant complexity. 因此,可以确保适当的释放能力,并且可以(实际上将)窃取内存,因为移动构造(除std::array )必然具有恒定的复杂性。

Note: There is still no guarantee for iterators to remain valid even for move construction. 注意:即使对于move构造,仍然不能保证迭代器保持有效。


On swap : 交换时

Demanding the iterators of two vectors to remain valid after a swap (now just pointing into the respective swapped container) is easy because swapping only has defined behaviour if 要求两个向量的迭代器在交换后保持有效(现在仅指向相应的交换容器)很容易,因为交换仅在以下情况下具有已定义的行为:

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

Thus, if the allocators do not propagate on swap and if they do not compare equal, swapping the containers is undefined behaviour in the first place. 因此,如果分配器在交换时不传播并且比较不相等,则交换容器首先是未定义的行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM