要迭代或使用计数器,这就是问题所在

时间:2009-12-06 19:24:43

标签: c++ stl coding-style

每当有人开始使用STL并且他们有一个向量时,您通常会看到:

vector<int> vec ;

//... code ...

for( vector<int>::iterator iter = vec.begin() ;
     iter != vec.end() ;
     ++iter )
{
  // do stuff
}

我发现整个vector<int>::iterator语法都在恶化。我知道你可以typedef vector<int>::iterator VecIterInt 稍微好一些......

但问题是,好的OL有什么问题:

for( int i = 0 ; i < vec.size() ; i++ )
{
  // code
}

12 个答案:

答案 0 :(得分:11)

当您使用索引执行对容器(std::vector或其他任何内容)的顺序访问时,您将随机访问要求强加于基础数据结构,实际上你的算法中不需要这种访问。与顺序访问的明显较弱的要求相比,随机访问要求是非常强烈的要求。没有充分理由强加更强的要求是一个重大的设计错误。

所以你的问题的正确答案是:尽可能使用顺序(迭代器)访问,只有在你必须时才使用随机(索引)访问。尽可能避免索引访问。

如果您的算法严格依赖于随机可访问的容器,它将成为算法的外部要求。在这种情况下,您可以使用索引访问而无需任何保留。但是,如果只能使用迭代器来实现相同的算法,那么最好只坚持使用迭代器,即完全依赖顺序访问。

当然,上述规则虽然属实,但在某些程度上只在代码泛型中才有意义。如果代码的其他部分是特定的,那么您确定您使用的数据结构是std::vector且始终是std::vector,那么访问方法不再重要。用你喜欢的任何东西。但是,在顺序访问完全足够的情况下,我仍然会避免索引访问。

答案 1 :(得分:7)

当谈到std::vector时,我认为在循环中使用下标运算符就好了,在性能方面可能大致相同。使用迭代器的优点在于您希望将向量与&lt; algorithms&gt;中的其他std函数一起使用。

答案 2 :(得分:2)

我认为我的论点不是很强,但我几乎总是使用迭代器版本。

typedef std::vector<int> MyIndexes; // or whatever
MyIndexes indexes;
for (Something::iterator iter = indexes.begin(); iter != indexes.end(); ++iter);

现在,如果我必须将矢量更改为列表或类似的东西,我只需要更改我的typedef。这在几个场合很有用。 auto关键字会让这更好,但我不能等待C ++ 0x for loop:)

答案 3 :(得分:1)

我通常对矢量使用for循环 - 对我而言,这是处理这些事情的规范方法。并且不要让任何人告诉你迭代器更快 - 请参阅What's faster, iterating an STL vector with vector::iterator or with at()?

另外,像foreach这样的迭代器和构造的极端支持者倾向于忽略的一件事是,使用for循环,你实际上有一个整数索引,你可以用除了访问集合元素以外的东西来做事情,这可能非常有用在各种情况下。

但是,如果我使用STL算法,或者迭代其他容器类型,例如std :: map,我当然会使用迭代器。

答案 4 :(得分:1)

作为一般规则,不限于std :: vector,迭代器可以比索引更有效,因为它们可以访问集合的内部并知道迭代的状态。

在std :: vector的情况下,循环中的迭代器将在使用优化编译时减少为指针算术。聪明的编译器可能能够对计数器循环执行相同的操作,并且性能不应该有任何差别,但一般情况下(对于更复杂的集合),您应该假设迭代器将为您提供最快的代码。

答案 5 :(得分:1)

使用C ++ 0x你不会有这种困境。您将使用新的for which is actually foreach:)

答案 6 :(得分:1)

我建议使用迭代器只是因为它们更通用。如果在开发周期的后期,您决定使用std :: list&lt;&gt;会比std :: vector&lt;&gt;更好(也许你发现你有一个性能问题,因为你需要从容器的头部弹出元素),更改迭代器版本将花费更少的精力。

答案 7 :(得分:1)

如果你在一个范围内创建,使用和销毁一个向量,那么使用迭代器抽象并没有多大好处,但对于更丰富的容器,例如std::map,开销变得值得。

迭代器非常适合泛型编程。机器和簿记都没有掩盖代码的意图,你获得了很大的灵活性。想把一个矢量复制到另一个吗?

std::vector<int> v1, v2;
// ...

std::copy(v1.begin(), v1.end(), v2.begin());

如何改为std::cout

std::ostream_iterator<int> output(std::cout, " ");
std::copy(v1.begin(), v2.end(), output);

迭代器允许单个模板处理各种情况。

答案 8 :(得分:0)

Personaly,我更喜欢使用迭代的迭代器。显示意图比通过索引,恕我直言访问更好,并且编码习语对于不同的容器是相同的。

答案 9 :(得分:0)

总是有两种想法。对于计算机来说无关紧要,编译器足够大,可以自我管理并最终为这两种情况生成同样好的代码。

但程序员怎么样?

看到for(int i=0;i<blah.size();i++)我的眼睛立刻将其视为“循环所有元素” 看到了:

typedef std::vector<int> MyIndexes; 
MyIndexes indexes;
for (Something::iterator iter = indexes.begin(); iter != indexes.end(); ++iter);

我必须仔细阅读每一行,检查你是不是在做一些棘手的事情。

另一方面,看到for()循环让我担心代码是由一个C程序员编写的,他对STL一无所知,并且设计得非常糟糕。

答案 10 :(得分:0)

标记的答案是错误的。这个代码完全没有“访问”。

在这种情况下,真正的答案应该没有区别,除非向量实现非常无能,除非使用迭代器稍微慢一些。

存储在数组中的迭代器是一个非常愚蠢的概念。它就是这样做的,因为其他一些情况不能直接作为数组加入,所以更一般的是它们鼓励迭代器用于所有东西。尽管迭代器非常繁琐,但只在少数特殊情况下使用才有意义。

答案 11 :(得分:0)

IMO,使用其中任何一个都非常缺乏重点。迭代集合应该在算法中完成。很多时候,一个现有的算法可以很好地完成这项工作,但是当它没有时,你通常最好自己写一些实际可用作通用算法的东西。

这是一种奇怪的情况,其中通用代码通常比最专业的代码更简单(也更容易编写)。特别是,迭代器类型不是迭代器,而是std::map<std::string, my_type>::iterator,迭代器类型是一个模板参数,你可以找到方便的名字:

template <class iter>
void my_algorithm(iter a, iter b) { 
    for (iter i=a; i!=b; ++i) 
        do_stuff(*i); 
}

但我会重复:很多时候,你可以使用现有的算法。对于您引用的案例,似乎std::for_each(或Boost FOR_EACH)可能会很好地运作。