这个减量的顺序是否会调用未定义的行为?

时间:2015-05-10 07:58:53

标签: c++ stl containers undefined-behavior erase

我正在寻找确认,澄清这个代码是否定义良好。

通过将迭代器重新分配给容器的erase()函数的结果来擦除循环中容器的元素是很常见的。这个循环通常有点混乱:

struct peer { int i; peer(int i): i(i) {} };

int main()
{
    std::list<peer> peers {0, 1, 2, 3, 4, 5, 6};

    for(auto p = peers.begin(); p != peers.end();) // remember not to increment
    {
        if(p->i > 1 && p->i < 4)
            p = peers.erase(p);
        else
            ++p; // remember to increment
    }

    for(auto&& peer: peers)
        std::cout << peer.i << ' ';
    std::cout << '\n';
}

输出: 0 1 4 5 6

我想到,如果没有调用未定义的行为,以下内容可能会更加整洁:

struct peer { int i; peer(int i): i(i) {} };

int main()
{
    std::list<peer> peers {0, 1, 2, 3, 4, 5, 6};

    for(auto p = peers.begin(); p != peers.end(); ++p)
        if(p->i > 1 && p->i < 4)
            --(p = peers.erase(p)); // idiomatic decrement ???

    for(auto&& peer: peers)
        std::cout << peer.i << ' ';
    std::cout << '\n';
}

输出: 0 1 4 5 6

我认为这有效的原因如下:

  • peers.erase()将始终返回递增的p,因此再次递减它是安全的

  • peers.erase(p)制作了p副本,因此由于排序reference

    而无法使用错误的值 LI>
  • p = peers.erase(p)返回p&,因此减量对正确的对象reference进行操作。

但我有些疑惑。我担心我在同一个表达式中使用--(p)来调用错误的排序规则,其中p被用作参数,尽管它在纸上看起来没问题。

有人可以在这里看到我的评估问题吗?或者这个定义得很好吗?

2 个答案:

答案 0 :(得分:5)

这取决于您用于检测要删除的元素的条件。如果您尝试删除第一个元素,它将失败,因为erase将返回新的begin(),然后您将其递减。 This is illegal,即使您立即再次增加它。

为了避免这个错误,并且因为它更常见和可读,我会坚持使用第一个版本。

答案 1 :(得分:2)

第二个版本 - 正如@DanielFrey所述 - 错了,但是如果你不喜欢第一个版本,为什么不做那样的事情:

std::list<int> myList = { 0, 1, 2, 3, 4, 5, 6 };

myList.remove_if(
    [](int value) -> bool {
        return value > 1 && value < 4;
    }
);

/* even shorter version
myList.remove_if([](int value) -> bool {
    return (value > 1 && value < 4);
});
*/

for(int value : myList) {
    std::cout << value << " ";
}

这给出了输出:

0 1 4 5 6

Live example