C ++ STL:哪种迭代方法比STL容器更好?

时间:2009-04-04 08:22:57

标签: c++ stl iterator containers

这对你们中的一些人来说可能看起来很无聊,但是对于STL容器,以下哪两种迭代方法更好?的为什么

class Elem;
typedef vector<Elem> ElemVec;
ElemVec elemVec;

// Method 0
for (ElemVec::iterator i = elemVec.begin(); i != elemVec.end(); ++i)
{
    Elem& e = *i;
    // Do something
}

// Method 1
for (int i = 0; i < elemVec.size(); ++i)
{
    Elem& e = elemVec.at(i);
    // Do something
}

方法0看起来像更清晰的STL,但方法1使用较少的代码实现相同。对容器的简单迭代是在任何源代码中出现 all 的地方。因此,我倾向于选择方法1,这似乎可以减少视觉混乱和代码大小。

PS:我知道迭代器可以做的不仅仅是一个简单的索引。但是,请保持回复/讨论集中在如上所示的容器上的简单迭代。

9 个答案:

答案 0 :(得分:16)

第一个版本适用于任何容器,因此在模板函数中更有用,它可以将任何容器作为参数。即使对于矢量,它也可以略微提高效率。

第二个版本仅适用于矢量和其他整数索引容器。对于那些容器来说,它更具惯用性,C ++的新手很容易理解,如果你需要用索引做其他事情,这很有用,这并不罕见。

像往常一样,我担心没有简单的“这个更好”的答案。

答案 1 :(得分:8)

如果你不介意(非常?)小的效率损失,我建议使用Boost.Foreach

BOOST_FOREACH( Elem& e, elemVec )
{
    // Your code
}

答案 2 :(得分:6)

方法0更快,因此建议使用。

方法1使用size(),允许为O(1),具体取决于容器和stl实现。

答案 3 :(得分:4)

方法0的一些优点:

  • 如果你从矢量移动到另一个 容器循环保持不变,
  • 很容易从迭代器移动到 const_iterator如果需要,
  • 当c ++ 0x到达时,auto 打字会减少一些代码混乱。

主要缺点是在很多情况下你会扫描两个容器,在这种情况下索引比保留两个迭代器更清晰。

答案 4 :(得分:4)

以下迭代标准库容器的方法最好。

使用range-based for-loop(及更高版本)的auto specifier

// Method 2
for (auto& e: elemVec)
{
    // Do something with e...
}

这与您的Method 0类似,但需要更少的输入,更少的维护,并且适用于与std::begin()std::end()兼容的任何容器,包括普通阵列。

答案 5 :(得分:2)

巧合的是我最近进行了速度测试(用rand()填充10 * 1024 * 1024英寸)。
这些是3次运行,时间以纳秒为单位

vect[i] time      : 373611869  
vec.at(i) time    : 473297793  
*it = time        : 446818590  
arr[i] time       : 390357294  
*ptr time         : 356895778  

UPDATE:添加了stl-algorithm std :: generate,由于特殊的迭代器优化(VC ++ 2008),它似乎运行速度最快。时间以微秒为单位。

vect[i] time      : 393951
vec.at(i) time    : 551387
*it = time        : 596080
generate = time   : 346591
arr[i] time       : 375432
*ptr time         : 334612

结论:使用标准算法,它们可能比显式循环更快! (也是良好做法)

更新:以上时间处于I / O限制的情况下,我使用CPU绑定进行相同的测试(迭代一个相对较短的向量,它应该重复适合缓存,将每个元素乘以2并写入回到矢量)

//Visual Studio 2008 Express Edition
vect[i] time      : 1356811
vec.at(i) time    : 7760148
*it = time        : 4913112
for_each = time   : 455713
arr[i] time       : 446280
*ptr time         : 429595

//GCC
vect[i] time      : 431039
vec.at(i) time    : 2421283
*it = time        : 381400
for_each = time   : 380972
arr[i] time       : 363563
*ptr time         : 365971  

有趣的是,与for_each相比,VC ++中的迭代器和运算符[]相当慢(这似乎会使迭代器降级为指针通过一些模板 - 魔术来提高性能)。 在GCC中,访问时间仅对at()更糟,这是正常的,因为它是测试中唯一的范围检查函数。

答案 6 :(得分:2)

方法0,原因有几个。

  • 它更好地表达了您的意图,这有助于编译器优化和可读性
  • 不太可能出现一个错误
  • 即使用一个没有operator []
  • 的列表替换向量,它仍然有效

当然,最好的解决方案通常是解决方案2:std算法之一。 std :: for_each,std :: transform,std :: copy或其他你需要的东西。这有一些进一步的优势:

  • 它更好地表达了您的意图,并允许一些重要的编译器优化(MS的安全SCL对您的方法0和1执行边界检查,但将在std算法上跳过它)
  • 它的代码较少(至少在调用网站上。当然你必须编写一个函子或者其他东西来替换循环体,但是在使用网站上,代码会被清理掉很多,这可能就是这是最重要的。

通常,请避免过度指定代码。准确指定您想要完成的任务,而不是其他内容。 std算法通常是去那里的方法,但即使没有它们,如果你不需要索引i,为什么要这样做呢?在这种情况下,请使用迭代器。

答案 7 :(得分:1)

这取决于哪种类型的容器。对于vector,使用哪个可能无关紧要。方法0已经变得更加惯用,但正如大家所说,它们并没有太大的区别。

如果你决定使用list,原则上方法1将是O(N),但实际上没有列表at()方法,所以你不能甚至这样做。 (所以在某种程度上你的问题堆叠了套牌。)

但这本身就是方法0的另一个参数:它对不同的容器使用相同的语法。

答案 8 :(得分:0)

以上未考虑的可能性:取决于&#34;做某事&#34;的细节,可以同时使用方法0和方法1,您不必选择:

for (auto i = elemVec.begin(), ii = 0; ii < elemVec.size(); ++i, ++ii)
{
    // Do something with either the iterator i or the index ii
}

这样,找到索引或访问相应的成员都是以微不足道的复杂性获得的。