【问题标题】:std::list and std::for_each: where is my end?std::list 和 std::for_each:我的结局在哪里?
【发布时间】:2016-11-21 13:27:37
【问题描述】:

考虑以下最小示例:

#include <functional>
#include <algorithm>
#include <list>

int main() {
    std::list<std::function<void()>> list;
    list.push_back([&list](){ list.push_back([](){ throw; }); });
    std::for_each(list.cbegin(), list.cend(), [](auto &&f) { f(); });
}

它在运行时编译并抛出异常。
我的猜测是std::for_each 只执行了第一个 lambda,但显然我错了:如果我在列表末尾附加另一个 lambda,迭代也会到达那个 lambda。

让我们恢复示例(push_front 代替 push_backcrbegin/crend 代替 cbegin/cend):

#include <functional>
#include <algorithm>
#include <list>

int main() {
    std::list<std::function<void()>> list;
    list.push_front([&list](){ list.push_front([](){ throw; }); });
    std::for_each(list.crbegin(), list.crend(), [](auto &&f) { f(); });
}

由于前面的示例,我预计它也会编译并崩溃。
相反,它编译并且不会崩溃。这一次,推到列表最前面的函数没有被执行。

问题很简单:这样对吗?
为什么这两个例子如此违反直觉?

在第一种情况下,我期待一些不同的东西,但我错了,这不是问题。
无论如何,我会期望两个循环之间的连贯性。我的意思是,第二个函数在一种情况下执行,而在另一种情况下不执行,但是在这两种情况下我都从 begin 迭代到 end
我的推理有什么问题?

【问题讨论】:

  • 使用的编译器?
  • @GillBates GCC 6.1 和 clang 3.9 都按照问题中的描述工作。是否相关?我不认为这可能是编译器的问题。
  • 不应该确实添加 lambda(第一个示例)吗?迭代器不会像 std::vector 那样失效。
  • @skypjack 我想我现在明白你的意思了,谢谢你的澄清。当然,您不会在生产中编写这样的代码:)

标签: c++ foreach iterator stdlist


【解决方案1】:

说实话,你得到的结果似乎是我所期望的。让我们来看看你的第一个例子:

1.

list.push_back([&list](){ list.push_back([](){ throw; }); });

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[end]

2​.开始迭代列表

迭代 1:

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[end]
^
+-- current

f() 呼叫list.push_back([](){ throw; });

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[inner_lambda]----[end]
^
+-- current

迭代 2:(++current)

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[inner_lambda]----[end]
            ^
            +-- current

f() 呼叫throw;

结束



现在让我们换个方向。

首先,看看反向迭代器是如何表示的——这很重要(图片来自 cppreference):

重要的部分是:反向结束点到正常开始。但问题是,对于列表,可以begin 之前插入一些内容,但在end 之后是不可能的。该不变量被反向迭代器破坏。

1.

list.push_front([&list](){ list.push_front([](){ throw; }); });

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
|
|+-- list.rend().base()
||
||          +-- list.rbegin().base()
vv          v
[lambda]----[end]

2​.开始迭代列表

迭代 1:

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
|
|+-- list.rend().base()
||
||          +-- list.rbegin().base()
vv          v
[lambda]----[end]
    ^           ^
    |           +---- current
    |
    +--------- passed list.rend()

*current 产生[lambda]

f() 呼叫list.push_front([](){ throw; });

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
|
|+-- list.rend().base()
||
||                            +-- list.rbegin().base()
vv                            v
[inner_lambda]----[lambda]----[end]
                      ^           ^
                      |           +---- current
                      |
                      +--------- passed list.rend().base()

请注意,传递的list.rend().base() 没有改变 - 但它不再指向第一个(经过最后一个反转的)元素。

迭代 2:(++current)

+-- list.begin() (not necessarily what has been passed to for_each)
|
|+-- list.rend().base()
||
||                            +-- list.rbegin().base()
vv                            v
[inner_lambda]----[lambda]----[end]
                      ^  ^
                      |  +---- current
                      |
                      +--------- passed list.rend().base()

current == passed list.rend().base()

结束



现在让我们尝试另一个我的错误是这部分与向前迭代列表相关

1.

list.push_front([&list](){ list.push_front([](){ throw; }); });

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[end]

2​.开始迭代列表

迭代 1:

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[end]
^
+-- current

f() 呼叫list.push_front([](){ throw; });

当前的迭代器没有失效和/或指向其他地方而不是它已经指向的地方。

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[inner_lambda]----[lambda]----[end]
                  ^
                  +-- current

迭代 2:(++current)

列表状态:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[inner_lambda]----[lambda]----[end]
                              ^
                              +-- current

结束

【讨论】:

  • 它没有考虑实际的 begin 和实际的 end 迭代器被复制的事实,因此固定在std::for_each 中。在示例中,您只考虑 begincurrent。我的疑问是 end(让我说)changes
  • @skypjack end 不会改变,当前是您通过的开始。但是您的 lambda 对整个列表进行操作,而不是您传递的子集。因此,在第二个示例中,它会在当前开始之前添加一个元素。
  • 我明白你的意思。这对我来说有点违反直觉,但这并不意味着它实际上是错误。让我三思而后行。
  • vector thinking :-D ...谢谢你的详细解释,非常感谢。 ;-)
  • 它们看起来不错。无论如何,我会在晚上调查此事,所以请耐心等待。现在我不能这样做,因为我不在家。对不起。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多