我可以在 std::list 中移动元素而不会使迭代器或引用无效,但是如何?

Posted

技术标签:

【中文标题】我可以在 std::list 中移动元素而不会使迭代器或引用无效,但是如何?【英文标题】:I can move elements within the std::list without invalidating iterators nor references, but how? 【发布时间】:2018-01-18 21:23:06 【问题描述】:

来自cppreference article on std::list

在列表中或跨多个列表添加、删除和移动元素不会使迭代器或引用无效。只有当相应的元素被删除时,迭代器才会失效。

确实,在对元素进行排序时就是这种情况。来自cppreference article on std::list::sort

这个函数也不同于std::sort,因为它不需要列表的元素类型是可交换的,保留所有迭代器的值,并执行稳定的排序。

但是如何在保留所有迭代器的值的同时任意交换两个元素的位置呢?

例如,假设我有一个列表:

std::list<int> l(1, 2, 3, 4);
auto it = l.begin(), jt = ++l.begin();

现在it 指向1jt 指向2。我可以重新排序这个列表,使2 出现在1 之前,但it 仍然指向1

我能做到:

std::swap(*it, *jt);

但是,虽然2 将出现在1 之前,但我不会保留迭代器的值,因为显然it 将指向2

鉴于 cppreference 的上述引用,我想应该可以实现我想要实现的目标;但是怎么做?

编辑:为了清楚起见:再举一个例子:

std::list<int> l(2, 1, 3, 4, 5);
auto it = l.begin(), jt = ++l.begin();

现在it 指向2jt 指向1

std::list::sort 具有我正在寻找的语义:

l.sort();

现在列表的顺序是:1, 2, 3, 4, 5,但it 仍指向2jt 仍指向1

另一方面,std::swap(it, jt)std::swap(*it, *jt) 都没有我想要的语义。调用其中任何一个将使it 指向1jt 指向2。 (ideone proof)

【问题讨论】:

这是一个双向链表。移动元素时,您只需调整指向和指向相邻元素的指针。 双向链表中的元素在交换时不会“移动”。他们指向下一个和前一个元素的指针会改变 @HenriMenke 如果我自己实现一个双向链表,那就太简单了;但是如何使用std::list 做到这一点? 看看***的文章“双链表”,你就会明白它是如何工作的,你会发现自己是怎么做的。 std::list::splice 【参考方案1】:

但是如何在保留所有迭代器的值的同时任意交换两个元素的位置呢?

按照@cpplearner 的建议,使用.splice()

它可以对单个元素或范围进行操作。它还可以跨列表传输元素。

这是一个简单的示例,演示如何移动单个元素。

std::list<int> list1,2,3,4,5;

// This element will be moved
auto source = std::find(list.begin(), list.end(), 4);

// It will be inserted before this element
auto destination = std::find(list.begin(), list.end(), 2);

list.splice(destination, list, source);
// ^                      ^
// |                      `- A list to move from
// `- A list to move to   

// Prints `1 4 2 3 5`.
for (int it : list) std::cout << it << ' ';

【讨论】:

谢谢。我必须说,起初我对what cppreference has to say about splice 感到困惑:“将元素从一个列表转移到另一个”。因此,我的第一印象是,使用 splice 在同一个列表中移动元素可能是某种 UB。然而,值得注意的是,在复杂性分析中,cppreference 明确提到了 splice 可以用于一个列表而不是两个列表的可能性,并且它似乎在实践中有效(ideone swap example)【参考方案2】:

您正在交换值。不是迭代器值。所以迭代器保持不变,而它们指向的值发生变化(被交换)。

至少在这方面考虑像指针一样的迭代器。

所以你会做些什么来交换迭代器和值就是:

std::swap(*it, *jt);
std::swap(it, jt);

【讨论】:

OP 正在询问如何使用 std::list 进行操作,而不是为什么它可能。 我明白了。我会改变我的答案。 "那么你将如何交换迭代器和值就是:std::swap(*it, *jt); std::swap(it, jt);"经测试。不工作。 ideone.com/bpyMs2 他想要改变元素的顺序。那是不行的。 @SidS OP 希望利用链表的特殊属性通过调整内部指针而不是交换内容来重新排序元素。

以上是关于我可以在 std::list 中移动元素而不会使迭代器或引用无效,但是如何?的主要内容,如果未能解决你的问题,请参考以下文章

序列容器-元素只能按顺序访问

std::list

从嵌套在 std::map 中的 std::list 中删除元素的最佳方法

在 C++ 中访问列表列表的元素

如何获得std :: list ::来自该列表元素的iterator?

指向 std::vector 和 std::list 元素的指针