list::iterator 对移至列表无效?

list::iterator invalid for moved-to list?

我使用了相当多的 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 move 是否会更改列表迭代器?为什么?

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

的 MSYS2 下

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

那么移动之后,vector的buffer还是一样的 但它不再属于 l,所以地址 past-the-end 此缓冲区的等价于 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';

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

但是 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 结束迭代器包含一个空指针和一个指向列表的指针。