【问题标题】:why does std::for_each iterator need a copy constructable iterator为什么 std::for_each 迭代器需要一个可复制构造的迭代器
【发布时间】:2015-01-04 09:47:34
【问题描述】:

我注意到 std::for_each 需要它的迭代器来满足 InputIterator 的要求,而 InputIterator 又需要 Iterator 然后是 Copy{Contructable,Assignable}。

这不是唯一的,std::for_each 实际上使用了复制构造函数 (cc)(就我的配置而言不是赋值)。即从迭代器中删除 cc 会导致:

error: use of deleted function ‘some_iterator::some_iterator(const some_iterator&)’

为什么 std::for_each 需要抄送?我发现这特别不方便,因为我创建了一个迭代器,它递归地遍历文件夹中的文件,跟踪队列中的文件和文件夹。这意味着迭代器有一个队列数据成员,如果使用 cc,也必须复制该成员:这是不必要的低效。

奇怪的是在这个简单的例子中没有调用cc:

#include <iostream>
#include <iterator>
#include <algorithm>


class infinite_5_iterator
:
public std::iterator<std::input_iterator_tag, int>
{
public:
  infinite_5_iterator() = default;
  infinite_5_iterator(infinite_5_iterator const &) {std::cout << "copy constr "; }

  infinite_5_iterator &operator=(infinite_5_iterator const &) = delete;


  int operator*() { return 5; }
  infinite_5_iterator &operator++() { return *this; }
  bool operator==(infinite_5_iterator const &) const { return false; }
  bool operator!=(infinite_5_iterator const &) const { return true; }
};

int main() {
  std::for_each(infinite_5_iterator(), infinite_5_iterator(),
    [](int v) {
      std::cout << v << ' ';
    }
  );
}

来源:http://ideone.com/YVHph8

但是需要编译时间。为什么 std::for_each 需要复制构造迭代器,什么时候完成?这不是非常低效吗?

注意:我说的是迭代器的 cc,而不是它的元素,就像这里所做的那样:unexpected copies with foreach over a map

编辑:请注意,该标准根本没有说明复制构造函数被调用,它只是表达了 f 被调用的次数。那么我可以假设根本没有调用 cc 吗?为什么没有指定使用operator++和operator*和cc,而使用f是?

【问题讨论】:

  • “未调用”。允许编译器优化复制。但是,这并没有改变在某些情况下必须提供复制 ctor 的事实。
  • 是的,它是必需的,但不一定要调用。我的问题是,何时/可以调用它。这对for_each的效率很重要,是不是叫做each迭代器?一开始只有一次?它从来没有被调用过吗?此外,为什么它真的需要。我不明白为什么 std::for_each 只能使用引用和前缀++。我可以编写一个迭代迭代器的 for 循环,但不需要 cc 或 ca。这是否意味着 for 循环比 std::for_each 更有效?
  • std::for_each 按值接收其参数。仅此一项就需要一个可访问的 cc,无论对它们施加什么额外要求。
  • 我也明白了,我很高兴看到我的编译器在那里应用了复制省略。然而,std::for_each 是否可以更频繁地复制迭代器,甚至每次迭代?考虑一个内部带有向量的迭代器,保持位置的索引。如果每次迭代都复制该迭代器,则迭代元素需要 O(n*n) 而不是 O(n):因此了解 std::for_each 的效率很重要。
  • 标准对此只字未提,由实施决定。如果您的迭代器需要大量复制,则可能会导致性能下降。迭代器不应该很重。您可以考虑另一种具有 O(1) 复制的内部簿记数据结构(例如,具有共享节点的基于单链表的堆栈)。

标签: c++11 stl iterator


【解决方案1】:

您只是成为几十年来逐渐演变的规范的牺牲品。 InputIterator 的概念早在移动类型或可移动类型的概念被构思出来之前很久就发明了。

事后看来,我很想声明InputIterator 不需要是可复制的。这将与它的单通道行为完美结合。但我也担心这样的更改会产生压倒性的向后兼容性问题。

除了标准中规定的有缺陷的迭代器概念外,大约十年前,为了提供帮助,gcc std::lib (libstdc++) 开始将“概念”强加于 InputIterator 之类的东西标准算法。 IE。因为标准说:

要求: InputIterator 应满足输入迭代器 (24.2.3) 的要求。

然后“概念检查”被插入到标准算法中,要求InputIterator 满足输入迭代器的所有要求无论算法是否实际使用了所有这些要求。在这种情况下,是概念检查,而不是实际算法,要求您的迭代器为 CopyConstructible

如果您编写自己的 for_each 算法,则无需将迭代器设为 CopyConstructibleCopyAssignable(如果提供了右值迭代器参数)就很简单:

template <class InputIterator, class Function>
inline
Function
for_each(InputIterator first, InputIterator last, Function f)
{
    for (; first != last; ++first)
        f(*first);
    return f;
}

对于您的用例,我建议您这样做,或者只是编写自己的循环。

【讨论】:

  • 您提供的代码是“您自己的”还是标准的?我发现它和这里一样cplusplus.com/reference/algorithm/for_each/?kw=for_each
  • @MarsonMao:标准没有指定for_each的实现。我已经为两个商业 std::lib 产品实现了这个功能,这就是我两次实现它的方式,模数在任何地方都加上下划线。没什么大不了的。
  • @HowardHinnant 谢谢。也许 std::for_each 指定(在标准中)在“复杂性”下(最多)多久调用一次 cc 可能是一个好主意?这样就不需要构建轻量级的单通道迭代器。
  • @HowardHinnant 为什么现在不能删除或放松这些“概念检查”以允许在std::for_each 中使用用户定义的不可复制迭代器? AFAIU 这不能破坏向后兼容性,因为它只会拓宽 STL 算法的适用性。
  • @alexeykuzmin0:我不确定您指的是实现还是标准规范。如果您指的是实现,请针对该实现提交错误以执行此操作。 libc++(例如)已经没有这个繁琐的概念检查:github.com/llvm-mirror/libcxx/blob/master/include/…。如果你说的是改变标准,那就更难了。您需要加入委员会(大门敞开,isocpp.org)。
猜你喜欢
  • 2019-08-13
  • 1970-01-01
  • 2021-01-19
  • 2017-02-19
  • 2012-04-16
  • 2016-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多