繁体   English   中英

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

[英]Does moving a vector invalidate iterators?

如果我在向量a有一个迭代器,那么我从a移动构造或移动分配向量b ,那么该迭代器是否仍指向同一元素(现在在向量b )? 这就是我在代码中的意思:

#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;
}

由于a已移至ba_iter仍然有效,还是该迭代a_iter迭代器无效? 作为参考, std::vector::swap 不会使迭代器无效

尽管可以合理地假设在move之后iterator仍然有效,但我认为Standard并不能真正保证这一点。 因此,在move之后,迭代器处于未定义状态。


没有引用我可以在其中明确指出 ,之前已经存在迭代器的标准找到move仍然是有效的 move

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

但是iterator的实现是实现定义的。 意思是,只要特定平台上的iterator满足标准规定的要求,就可以以任何方式实施。 从理论上讲,它可以实现为返回vector类的指针和索引的组合。 如果真是这样,那么在move之后,迭代器将变得无效。

iterator是否实际以这种方式实现是无关紧要的。 可以通过这种方式来实现,因此,如果标准中没有明确保证move后迭代器仍然有效,则不能假设它们是有效的。 还请记住, swap之后,迭代器具有这样的保证。 这是从先前的标准中明确阐明的。 也许这只不过是标准comittee不要做出类似的澄清后,迭代器的监督move ,但在任何情况下,有没有这样的保证。

因此,总而言之,你不能认为move之后迭代器仍然是好的。

编辑:

第n3242号草案中的23.2.1 / 11指出:

除非另有说明(显式指定或通过在其他函数中定义一个函数),否则调用容器成员函数或将容器作为参数传递给库函数均不得使对该容器内对象的迭代器或更改其值无效。

这可能导致人们得出以下结论:迭代器在move之后是有效的,但我不同意。 在您的示例代码中, a_itervector a的迭代器。 在之后move ,该容器, a肯定已经改变了。 我的结论是以上条款不适用于这种情况。

我认为将移动构造更改为移动分配的编辑会更改答案。

至少如果我正确地读取了表96,则移动构造的复杂性将以“ note B”的形式给出,这对于除std::array以外的任何事物都是恒定的。 但是,移动分配的复杂度是线性的。

这样,移动构造基本上别无选择,只能从源复制指针,在这种情况下,很难看到迭代器如何变得无效。

但是,对于移动分配,线性复杂度意味着它可以选择将单个元素从源移动到目的地,在这种情况下,迭代器几乎肯定会变得无效。

元素移动分配的可能性通过以下描述得到了增强:“ a的所有现有元素都被分配或销毁了”。 “销毁”部分将对应于销毁现有内容,并从源头“窃取”指针,但是“移动到”将指示将各个元素从源头移到目的地。

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

tl; dr:是的,移动std::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);

现在如果

  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将分配新的存储并将a元素一一移动到该存储中,从而使所有迭代器,指针和引用无效。

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

如果移动分配的分配器传播或者如果两个分配器比较相等,那么实施将很有可能选择只让b自己a s的数据,因为它可以确保能够正确地解除分配存储。

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 )。


在施工中

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

知道分配器的容器的move构造函数将从当前表达式要从其移动的容器的分配器实例移动构造其分配器实例。 因此,可以确保适当的释放能力,并且可以(实际上将)窃取内存,因为移动构造(除std::array )必然具有恒定的复杂性。

注意:即使对于move构造,仍然不能保证迭代器保持有效。


交换时

要求两个向量的迭代器在交换后保持有效(现在仅指向相应的交换容器)很容易,因为交换仅在以下情况下具有已定义的行为:

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

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

暂无
暂无

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

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