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

时间:2011-04-11 03:22:49

标签: c++ design-patterns idioms

据我所知,迭代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 ++的新手。)

4 个答案:

答案 0 :(得分:5)

我绝对是STL的ga-ga。但我记不起实际上曾经使用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 )

答案 1 :(得分:2)

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

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

它还引入了范围适配器的概念,它可以用算法组成,以增加它们的有用性和通用性。

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

答案 2 :(得分:1)

你的建议没有任何问题,尽管我不得不说Boost.ForEach与现在的C ++世界中的事物接近“惯用语”。

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

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

是您也可以内联您的操作,而不是严格限制显式功能映射。

答案 3 :(得分:1)

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

您在std::for_each的第三个参数中看到了类似的问题。理论上,lambda存在。在C ++ 98中,他们没有。因此,您必须在线外定义仿函数,对绑定器和合成器使用详细语法,或使用无状态指针到函数。

就个人而言,我认为所有STL算法都应该采用一个范围(单个对象),而不是一对迭代器。将后者包含在一个范围内是微不足道的。现在你看到ostream_iterators必须定义一个相当任意的结束对象。