为什么总是在std::for_each中指定迭代器?

Why always specify iterators in std::for_each?

本文关键字:迭代器 each for std 为什么      更新时间:2023-10-16

据我所知,遍历STL集合的习惯用法是这样的:

int a[] = { 1,2,3 };
std::vector<int> v(a, a+3);
std::for_each(a.begin(), a.end(), some_function);

如果我只想处理集合的某个范围,或者做一些更有创意的事情,指定第一个和最后一个迭代器是有用的,但大多数时候,我怀疑我们实际上想处理整个集合。所以,我想知道为什么人们在这种情况下要费心指定迭代器(因为它们总是相同的),而不只是沿着这些行使用一个方便的函数:

namespace easy
{
  template <class T, class F>
  F for_each(T& collection, F function)
  {
    std::for_each(collection.begin(), collection.end(), function);
    return function;
  }
}

(当然,有可能这个做事的习惯方式,我从来没有注意到!不过我是c++新手。)

我非常喜欢STL。但是我不记得实际上曾经使用过for_each

成语是

for ( container::iterator it = coll.begin(); it != coll.end(); ++ it )

c++ 11引入了糖来将其还原为

for ( auto elem : coll )

这类似于你的便利函数,但使用自由(非成员)std::beginstd::end函数,允许与非标准容器的对象兼容。

另外,查找它(我还没有玩这个,因为它还没有在GCC中),它看起来限制了程序员访问范围的元素,而不是迭代器。


至于使用容器来引用其整个范围,最好保持允许子范围的灵活性。另一种解决方案是为迭代器引入一种习惯用法,{ begin, end }。有一些争论,我期望c++ 11包含这样一个特性

begin( make_pair( begin_, end_ ) ) // == begin_,
end( make_pair( begin_, end_ ) ) // == end_,
for ( auto elem : make_pair( begin_, end_ ) ) // iterates over [ begin_, end )

但是在阅读标准时,看起来pair缺少这个功能。

但是,您可以创建自己的pair,以获得灵活的基于范围的for:

template< typename iter >
struct range_type {
    iter first, last;
    // use friends because Standard specifies these should be found by ADL:
    friend iter begin( range_type const &r ) { return r.first; }
    friend iter end( range_type const &r ) { return r.last; }
};
template< typename iter >
range_type< iter > range( iter first, iter last )
    { return range_type< iter >{ first, last }; }
// usage:
for ( auto elem : range( begin_, end_ ) ) // iterates over [ begin_, end )

指定开始和结束迭代器,而不是仅仅指定集合,确实很乏味,而且容易出错(例如,您的示例代码错误地试图在a而不是v上调用.begin().end())。这就是Boost Range被发明的原因。有了它,您可以编写如下代码:

int a[] = { 1,2,3 };
boost::for_each(a, some_function);

还介绍了范围适配器的概念,它可以与算法组成,以增加其有用性和通用性。

[推测]STL使用迭代器而不是范围的原因是,它是从构造算法的角度出发的,然后找到这些算法的最小需求,并用这些需求来表达它们。算法需要迭代器来完成它们的工作,即使算法使用的自然对象实际上是值的范围。正如在另一个答案中提到的,STL面临着严重的时间压力,因此这些问题从未得到解决。幸运的是,我们现代人有Boost。范围,所以我们可以使用基于范围的算法

你的建议没有错,尽管我不得不说Boost。在当今的c++世界中,ForEach几乎是最接近"惯用"的了。

这种方法的好处,它在使用中看起来像:

BOOST_FOREACH(value_type i, collection) {
    function(i);
}

是你也可以内联你的操作,而不是严格地绑定到一个显式的函数映射。

原因是STL的设计者来自理论方面。在Comp.Sci中,范围是一个比容器更基本的概念。实际上,容器要常见得多。STL是在1996-1998年的时间压力下添加的,并且没有进行积极的重构。

您可以在std::for_each的第三个参数中看到类似的问题。理论上,是存在的。在c++ 98中,它们没有。因此,您必须定义一个离线的函子,使用绑定器和组合器的详细语法,或者使用一个无状态的指向函数的指针。

我个人认为,所有STL算法都应该接受一个范围(单个对象),而不是一对迭代器。将后者包装在一个范围中是很简单的。现在您看到ostream_iterators必须定义一个相当任意的end对象。