【问题标题】:list::iterator invalid for moved-to list?list::iterator 对移至列表无效?
【发布时间】:2020-12-25 14:17:00
【问题描述】:

我使用了相当数量的 C++,但没有那么多 std::list .. 在我当前的项目中,我需要一个std::list<..> 数据成员,并使用std::list<..>::iterator 跟踪列表中的位置。该对象也必须是可移动的,但在我的情况下,默认的移动构造函数是不可能的。这里std::list 做了一些让我吃惊的事情。

考虑

#include <list>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

template<typename T>
void test() {
    T l { 1, 2, 3, 4, 5 };
    cout << "l = "; for(const auto& e: l) cout << e << " "; cout << endl;
    auto pos = find(l.begin(), l.end(), 6);
    if (pos == l.end()) cout << "l end\n";

    cout << "---- moving l > lmv ----" << endl;
    T lmv { std::move(l) };
    cout << "l = "; for(const auto& e: l) cout << e << " "; cout << endl;
    cout << "lmv = "; for(const auto& e: lmv) cout << e << " "; cout << endl;
    if (pos == l.end()) cout << "l end\n";
    if (pos == lmv.end()) cout << "lmv end\n";
}

int main() {
    cout << "___vector___\n";
    test<vector<int>>();
    cout << "___list___\n";
    test<list<int>>();
}

这个输出

___vector___
l = 1 2 3 4 5 
l end
---- moving l > lmv ----
l = 
lmv = 1 2 3 4 5 
lmv end
___list___
l = 1 2 3 4 5 
l end
---- moving l > lmv ----
l = 
lmv = 1 2 3 4 5 
l end

即指向从lists 移动端的迭代器不指向移动到lists 端。 但它适用于vector,如果迭代器本质上是指针,这是我一直期望的。为什么list 不同?元素的内存位置不应该随着移动而改变.. lists 移动改变列表迭代器吗?为什么?

我正在使用“g++.exe(Rev1,由 MSYS2 项目构建)10.2.0” 在 Windows 10 上的 MSYS2 下

【问题讨论】:

  • 当你的程序在 Visual C++ 调试模式下运行时,你的程序会以assert() 大爆炸。
  • 另外,比较指向不同容器的迭代器没有意义。
  • @Someprogrammerdude 根据cppreference std::list 中的迭代器在移动后有效,std::vector 的也是如此
  • 虽然end 迭代器似乎有一个例外,它可以失效
  • @Daniel Langr 我会同意,如果容器实际上不同,但这是一个举动..?

标签: c++ list iterator move-semantics


【解决方案1】:

如果您在测试函数的末尾添加了如此可怕的行 (这完全不正确,消毒剂会侮辱你!), 您可以看到,在 vector 的情况下,end() 迭代器 指定缓冲区结束后包含的内容 存储的元素,但在 list 的情况下是结束迭代器 指定存储在list 中的某种标记 结构本身。

那么,移动之后,vector的buffer还是一样的 但它不再属于l,所以地址过去了 这个缓冲区的大小相当于end() for lmv

另一方面,在移动list后,pos指定 地址 inside l 仍然指定相同的地址(尽管 l 被移出)但未指定内部的 end() 标记 lvm 在初始化 pos 时甚至都不存在。

    std::cout << "pos: " << (void *)(&*pos) << '\n';
    std::cout << "l: " << (void *)(&l) << '\n';
    std::cout << "l.begin(): " << (void *)(&*l.begin()) << '\n';
    std::cout << "l.end(): " << (void *)(&*l.end()) << '\n';
    std::cout << "lmv: " << (void *)(&lmv) << '\n';
    std::cout << "lmv.begin(): " << (void *)(&*lmv.begin()) << '\n';
    std::cout << "lmv.end(): " << (void *)(&*lmv.end()) << '\n';

【讨论】:

    【解决方案2】:

    在移动容器时应该保留迭代器。

    但是,容器的 end 迭代器不指向元素,因此允许为 invalidated when moving a container

    如果您将代码更改为使用begin 而不是end,那么它可以使用as you expect

    #include <list>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    template<typename T>
    void test() {
        T l { 1, 2, 3, 4, 5 };
        cout << "l = "; for(const auto& e: l) cout << e << " "; cout << endl;
        auto pos = find(l.begin(), l.end(), 1);
        if (pos == l.begin()) cout << "l begin\n";
    
        cout << "---- moving l > lmv ----" << endl;
        T lmv { std::move(l) };
        cout << "l = "; for(const auto& e: l) cout << e << " "; cout << endl;
        cout << "lmv = "; for(const auto& e: lmv) cout << e << " "; cout << endl;
        if (pos == l.begin()) cout << "l begin\n";
        if (pos == lmv.begin()) cout << "lmv begin\n";
    }
    
    int main() {
        cout << "___vector___\n";
        test<vector<int>>();
        cout << "___list___\n";
        test<list<int>>();
    }
    

    请注意,比较来自两个不同容器的迭代器是未定义的行为,因此最终的 pos == l.begin() 是未定义的行为,并且 Visual Studio 的调试版本至少会在运行此代码时抛出断言。

    我想你的原始代码可以工作,因为 std::vector end 迭代器通常只是实现为指向最后一个元素之后的一个。我想std::list 结束迭代器包含一个空指针和一个指向列表的指针。

    【讨论】:

    • 我明白了.. 谢谢。我知道我的测试功能结束时的恐怖 - 这基本上是故意 UB 只是为了看到一些东西,因为我不知道发生了什么。
    猜你喜欢
    • 2016-03-31
    • 2011-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多