擦除外部迭代器



我试验了以下代码

list<int> a ={1,2};
list<int> b ={3,4};
a.erase(b.begin());
for (auto x:b) cout << x << endl;

奇怪的是,程序运行良好,没有任何错误。它打印出来的是4。我想知道当对象已经隐含在迭代器中时,为什么erase是一个成员函数。

a.erase(b.begin());

这会调用未定义的行为,因为您正在将从一个容器获得的迭代器传递给另一个容器的函数。ab是两个不同的容器,它们并不相同。

未定义的行为意味着任何事情都可能发生:它可能按预期运行,也可能不按预期运行。语言规范和编译器都不能保证它能工作。它被恰当地称为"未定义的行为"。

你应该做的是:

auto value = *(b.begin()); //value is int
auto it = std::find(a.begin(), a.end(), value); //returns iterator 
if ( it != a.end())
   a.erase(it); //well-defined, as the iterator belongs to the same container!

或者,如果你想删除所有等于value的元素,那么你可以简单地这样做:

a.remove(value); //std::list has remove member function

但是,如果您使用std::vector,在大多数情况下都应该使用它。它是C++中的默认容器类型,只有在有充分理由的情况下才应该使用std::list

std::vector<int> a ={1,2};
std::vector<int> b ={3,4};
//if you want to remove one element:
auto value = *(b.begin()); //value is int
auto it = std::find(a.begin(), a.end(), value); //returns iterator 
if ( it != a.end())
   a.erase(it); //well-defined, as the iterator belongs to the same container!

如果你想删除所有等于value的元素,那么你可以将流行的Erase-remove Idiom应用为:

a.erase(std::remove(a.begin(), a.end(), value), a.end()); 

请注意,std::vector没有remove()成员函数,这就是应用这个习惯用法的原因。你可以在这里阅读我的回答,其中更详细地讨论了这一点。

这只是未定义的行为,所以任何事情都可能发生,而您观察到的任何在某种程度上都是"预期行为"。

不要这样做。

std::list::erase的前提条件显然是参数是容器的元素的迭代器

C++是一个标准,它不需要迭代器知道它属于哪个容器。我们不能仅仅因为在一个特定的实现中,函数可以在不需要特定参数的情况下完成它的工作而更改标准。

其他人提到,根据C++标准,这会导致未定义的行为。

然而,双链表的美妙之处在于,从列表中删除节点只需要指向该节点的指针,而不需要引用容器本身,例如:

template<class Tag>
inline void unlink(ListNode<Tag>* node)
{
    ListNode<Tag> *prev = node->prev_, *next = node->next_;
    prev->next_ = next;
    next->prev_ = prev;
    node->prev_ = node;
    node->next_ = node;
}

您的代码恰好工作正常,因为std::list<>通常被实现为一个双链表,而list<>::erase()从迭代器中获取一个指向列表节点的指针,并执行类似于上面的代码。但是,如果您启用迭代器的调试支持,则此代码可能会导致运行时断言。

如果std::list::erase实际上并不需要this的知识,那么这只是一个实现细节。它是一个成员函数,用于与其他容器保持一致。实际上,您可以将容器作为模板参数,并调用container.erase(iter);

最新更新