【问题标题】:Does std::vector::insert() invalidate iterators if the vector has enough room (created through reserve)?如果向量有足够的空间(通过保留创建),std::vector::insert() 是否会使迭代器无效?
【发布时间】:2013-01-27 01:37:46
【问题描述】:

回答How to self-copy a vector? 让我对迭代器失效感到有些困惑。一些文献说“如果您使用 insert、push_back 等,请考虑所有迭代器无效”。很明显,它可能会导致向量增长,从而使迭代器无效。我知道会有足够空间的特殊情况呢?

第一次尝试:

myvec.reserve(myvec.size()*3);  //does this protect me from iterator invalidation?
vector<string>::iterator it = myvec.end();    
myvec.insert(myvec.end(), myvec.begin(), it);
myvec.insert(myvec.end(), myvec.begin(), it);

在一些优秀的答案后第二次尝试:

auto size = myvec.size();
myvec.reserve(size*3);  //does this protect me from iterator invalidation?  
myvec.insert(myvec.end(), myvec.begin(), myvec.begin()+size);
myvec.insert(myvec.end(), myvec.begin(), myvec.begin()+size);

经过更优秀的答案第三次尝试:

auto size = myvec.size();
myvec.reserve(size*3);  //does this protect me from iterator invalidation?  
back_insert_iterator< vector<string> > back_it (myvec);
copy (myvec.begin(),myvec.begin()+size,back_it);
copy (myvec.begin(),myvec.begin()+size,back_it);

这句话来自 Josuttis 的“C++ 标准库参考”:

插入或删除元素 使引用、指针和 引用以下内容的迭代器 元素。如果插入导致 重新分配,它使所有 引用、迭代器和指针。

表明我的代码是安全且已定义的行为。标准中是否有段落可以保证这一点?

【问题讨论】:

  • 只为那些想要继续搜索的人:是的,某处有一段,我只是手头没有。它说的几乎和 josuttis 书完全一样。
  • 我手边没有标准,但最好的事情是,cppreference.com 指出以下关于std::vector&lt;T&gt;::insert() 的内容:"如果新的 size() 大于旧容量()。如果新大小()大于容量(),则所有迭代器和引用都无效。否则,只有添加元素之后的迭代器和引用无效。”我会参考标准,但与其他网站不同的是,cppreference 由按照标准生存、呼吸和死亡的贡献者维护。

标签: c++ vector stl iterator c++-standard-library


【解决方案1】:

迭代器不应在中间函数中失效。内存可能被重新定位的想法并不成立,因为您不能在具有非平凡构造函数的对象上使用realloc。即使构造不是问题,在最坏的情况下它仍然必须复制初始序列两次,从而抵消了平均情况下的任何好处。

重点是,以这种方式实现它是没有意义的;不管标准怎么说,alloccopyfree 几乎肯定已经完成。

这是安全的,因为 v.begin()v.end() 始终是最新的。

v.insert(v.end(), v.begin(), v.end());
v.insert(v.end(), v.begin(), v.end());

这不是。

vector<foo>::iterator i = v.begin();
vector<foo>::iterator j = v.end();
v.insert(v.end(), i, j);
v.insert(v.end(), i, j);

但是,自我插入可能很不稳定。在 GCC 下尝试以下操作。 只有如果有足够的可用内存(不确定这是否是错误),自插入会给出不正确的结果。

int main()
{
    int position = 1, first = 2, last = 3;
    // enforce error condition.
    assert(position < first);
    int size = 8;
    // sanity check.
    assert(first < last && last <= size);

    std::vector<int> right, wrong;
    // force resize during insertion.
    right.reserve(size);
    // avoid resize during insertion.
    wrong.reserve(size + (last - first));

    for ( int i = 0; i < size; i++ )
     {
       right.push_back(i);
       wrong.push_back(i);
     }

    std::vector<int>::iterator i;
    i = right.begin();
    right.insert(i + position, i + first, i + last);
    i = wrong.begin();
    wrong.insert(i + position, i + first, i + last);

    assert(right == wrong);
    return 0;
}

注意:上述观点特别适用于vector,而不是一般的容器。此外,上述行为可能是错误的建议与标准无关,而是为 vector 实现强大的自插入的难易程度。

【讨论】:

    【解决方案2】:

    尽管插入向量确实不会导致重新分配,只要不超过容量,并且不会使插入点之前元素的迭代器无效(可以说是@KerrekSB 指出的end() 的情况),C++11 标准的表 100(第 23.2.3 段)为序列容器的 a.insert(p,i,j) 函数指定了以下 前提条件

    [...] pre: i 和 j 不是 a 的迭代器。 [...]

    在您的情况下,它们显然是,这让我认为该程序具有未定义的行为。

    【讨论】:

    • 这几乎是问题的本质;)标准是否只是说因为插入可能会导致增长?也许,但另一方面,这不会经得起语言律师的考验......
    • @PorkyBrain:为什么标准说明它并不重要。事实上,它确实将其声明为前提条件就足够了:如果您违反它,则允许实现做任何事情
    【解决方案3】:

    过去的迭代器总是有点特别。我会小心的。标准是这样说的(23.3.6.5):

    如果没有发生重新分配,则插入点之前的所有迭代器和引用都保持有效。

    这里的关键是“在插入点之前”。由于您原来的 it 不在插入点之前(因为它插入点),所以我不会指望它保持有效。

    【讨论】:

    • 哇,你简直让我大吃一惊,因为它指向旧的结束,这是一个过去的理论值,当然它可以失效。 +1 我将修复我的代码以忽略此错误。
    • +1 感谢您查找此内容。我真的需要在我的工作清单上获得一份标准副本。
    • @WhozCraig:只是一个草稿,但对大多数用途都很好:open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3485.pdf
    • @PorkyBrain:我曾经有过similar situation :-)
    猜你喜欢
    • 2011-05-06
    • 2021-06-07
    • 1970-01-01
    • 1970-01-01
    • 2010-09-08
    • 2011-04-22
    • 2012-06-16
    • 1970-01-01
    相关资源
    最近更新 更多