【问题标题】:Why does a push_back on an std::list change a reverse iterator initialized with rbegin?为什么 std::list 上的 push_back 会更改用 rbegin 初始化的反向迭代器?
【发布时间】:2012-04-22 13:20:15
【问题描述】:

根据我发现的一些 STL 文档,在 std::list 中插入或删除元素不会使迭代器无效。这意味着它可以遍历一个列表(从begin()end()),然后使用push_front 添加元素。

例如,在下面的代码中,我用元素 a、b 和 c 初始化一个列表,然后循环遍历它并执行元素的 push_front。结果应该是cbaabc,这正是我得到的:

std::list<std::string> testList;
testList.push_back("a");
testList.push_back("b");
testList.push_back("c");

for (std::list<std::string>::iterator itList = testList.begin(); itList != testList.end(); ++itList)
   testList.push_front(*itList);

for (std::list<std::string>::const_iterator itList = testList.begin(); itList != testList.end(); ++itList)
   std::cout << *itList << std::endl;

当我使用反向迭代器(从rbegin() 循环到rend())并使用push_back 时,我预计会有类似的行为,即abccba 的结果。但是,我得到了不同的结果:

std::list<std::string> testList;
testList.push_back("a");
testList.push_back("b");
testList.push_back("c");

for (std::list<std::string>::reverse_iterator itList = testList.rbegin(); itList != testList.rend(); ++itList)
   testList.push_back(*itList);

for (std::list<std::string>::const_iterator itList = testList.begin(); itList != testList.end(); ++itList)
   std::cout << *itList << std::endl;

结果不是abccba,而是abcccba。没错,又多了一个c。

看起来第一个 push_back 也改变了用 rbegin() 初始化的迭代器的值。在 push_back 之后,它不再指向列表中的第三个元素(之前是最后一个),而是指向第四个元素(现在是最后一个)。

我使用 Visual Studio 2010 和 GCC 对此进行了测试,都返回了相同的结果。

这是一个错误吗?还是我不知道的反向迭代器的一些奇怪行为?

【问题讨论】:

    标签: c++ stl iterator


    【解决方案1】:

    标准规定迭代器和引用在插入期间保持有效。它没有说明反向迭代器。 :-)

    rbegin() 返回的reverse_iterator 内部包含end() 的值。在push_back() 之后,这个值显然与以前不同。我认为标准没有说明它应该是什么。明显的替代方案包括列表的前一个最后一个元素,或者如果它是一个固定值(如哨兵节点),它会停留在末尾。


    技术细节:rend() 返回的值不能指向begin() 之前,因为那是无效的。所以决定rend() 应该包含begin() 的值,并且所有其他反向迭代器再移动一个位置。 operator* 对此进行了补偿并访问了正确的元素。

    24.5.1 反向迭代器的第一段说:

    类模板reverse_iterator是一个迭代器适配器,它从定义的序列的末尾进行迭代 通过其底层迭代器到该序列的开头。反向之间的基本关系 迭代器及其对应的迭代器 i 由身份建立:
    &amp;*(reverse_iterator(i)) == &amp;*(i - 1)

    【讨论】:

    • 谢谢,技术细节有参考吗?
    • 添加了标准的引用。
    • @BoPersson:请注意,“保持有效”仅涉及指向现有元素的迭代器。因此未指定奇异值(如end())的行为。
    • @MatthieuM - 它没有这么明确地说,至少在list 部分没有。它在 [list.modifiers] 中说“不影响迭代器和引用的有效性”。我会更喜欢“现有元素的迭代器”......
    • @BoPersson:在 23.1/11 中它说 除非另有说明 [...] 调用容器成员函数或将容器作为参数传递给库函数不应使迭代器无效,或更改该容器内对象的值。 明确区分。
    【解决方案2】:

    我认为要理解这一点,最好先将for 循环重新转换为while 循环:

    typedef std::list<std::string> container;
    
    container testList;
    testList.push_back("a");
    testList.push_back("b");
    testList.push_back("c");
    
    container::reverse_iterator itList = testList.rbegin(); 
    while (itList != testList.rend()) {
        testList.push_back(*itList);
         ++itList;
    }
    

    除此之外,我们还必须了解reverse_iterator 的一般工作原理。具体来说,reverse_iterator 确实指向元素 after,当您取消引用它时,您会得到它。 end() 产生一个迭代器,只在容器的末尾之后——但是对于像数组这样的东西,没有定义的方法来指向容器的开头之前。相反,C++ 所做的是让迭代器从结束之后开始,然后一直到开始,但是当你取消引用它时,你会得到它实际指向的 before 元素。

    这意味着你的代码实际上是这样工作的:

    在那之后,你得到了几乎你所期望的,推回 B 然后 A,所以你最终得到 ABCCCBA。

    【讨论】:

    • 你能指出规范,甚至是参考站点,它表明 reverse_iterators 以这种方式工作吗? (如果不是,那说明解释不一定错,但如果没有错,那就是实现错误)
    • @immibis: §[reverse.iterators]/1: “反向迭代器与其对应的迭代器 i 之间的基本关系由恒等式建立:&*(reverse_iterator(i)) == & *(i - 1)。”
    【解决方案3】:

    尝试对两者都使用迭代器。试试:

    std::list<std::string>::iterator i = testList.end(); 
    

    并使用 --i 反转

    【讨论】:

    • 我不是在寻找替代解决方案。我想知道为什么反向迭代器突然指向别的东西,因为这似乎与文档相矛盾(见sgi.com/tech/stl/List.html)。
    猜你喜欢
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    • 2021-03-18
    • 2018-08-13
    • 1970-01-01
    • 1970-01-01
    • 2012-02-09
    • 1970-01-01
    相关资源
    最近更新 更多