【问题标题】:Is the sequencing of this decrement invoking undefined behaviour?这种递减的顺序是否会调用未定义的行为?
【发布时间】:2015-05-10 07:58:52
【问题描述】:

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

通过将迭代器重新分配给容器的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 排序而对错误值进行操作

  • p = peers.erase(p) 返回一个p&amp;,因此减量作用于正确的对象reference

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

任何人都可以在这里看到我的评估有问题吗?还是这个定义好?

【问题讨论】:

  • 我会坚持使用第一个版本 - 一开始它更具可读性
  • 在排序方面没有问题,因为这些是重载的运算符 - 即函数调用。

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


【解决方案1】:

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

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

【讨论】:

  • 是的,我刚刚对此进行了检查,并且在标准中绝对不允许这样做。
【解决方案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.

【讨论】:

  • 这是个好主意。不幸的是,有时你想根据不同的条件做一些不同的事情——有时是擦除,有时是调用成员函数等。在这些情况下,你需要自己做循环。
  • @Galik - (1) OP 的示例都是关于删除特定项目,所以... ;) (2) 看看that - 你也可以修改值。 如果要删除该值,则返回 true,如果不删除该值,则返回 false。否则,这作为容器迭代器非常好,每个项目都调用了自定义函数。
猜你喜欢
  • 2023-03-30
  • 1970-01-01
  • 2019-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-19
  • 1970-01-01
相关资源
最近更新 更多