Iterator初始化for循环内部是不好的样式,为什么?

时间:2008-10-09 20:23:51

标签: c++ stl coding-style iteration iterator

通常你会发现这样的STL代码:

for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}

但我们实际上有建议像这样写:

SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
for (; Iter != IterEnd; ++Iter)
{
}

如果您担心范围界定,请添加括号:

{
    SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
    SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
    for (; Iter != IterEnd; ++Iter)
    {
    }
}

这应该可以提高速度和效率,特别是在编程控制台时,因为.end()函数不会在循环的每次迭代中调用。我只是把性能提升视为理所当然,这听起来很合理,但我不知道它有多少,它肯定取决于容器的类型和实际使用的STL实现。但是,使用这种风格已经有几个月了,我实际上更喜欢它而不是第一种。

可读性的原因:for line干净整洁。使用实际生产代码中的限定符和成员变量,如果您使用第一个示例中的样式,则很容易使真正长。这就是为什么我故意让它在这个例子中有一个水平滚动条,只是让你看到我在说什么。 ;)

另一方面,您突然将Iter变量引入for循环的外部范围。但是,至少在我工作的环境中,即使在第一个例子中,也可以在外部范围内访问Iter。

你对此有何看法?除了可能限制Iter的范围之外,还有第一种风格的专家吗?

13 个答案:

答案 0 :(得分:13)

如果您正确地将代码包装成行,则内联表单将具有相同的可读性。此外,您应始终将iterEnd = container.end()作为优化:

for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
    IterEnd = m_SomeMemberContainerVar.end();
    Iter != IterEnd;
    ++Iter)
{
}

更新:根据每个paercebal的建议修复代码。

答案 1 :(得分:9)

另一种方法是使用foreach宏,例如boost foreach

BOOST_FOREACH( ContainedType item, m_SomeMemberContainerVar )
{
   mangle( item );
}

我知道在现代c ++中不鼓励使用宏,但是直到auto关键字被广泛使用,这才是我发现的最简单和可读的东西,并且仍然完全类型安全且快速。您可以使用任何初始化样式来实现宏,从而获得更好的性能。

链接页面上还有一条关于重新定义BOOST_FOREACH作为foreach的说明,以避免烦人的全部上限。

答案 2 :(得分:5)

如果在for循环之后不需要迭代器,第一种形式(for循环内部)会更好。它将其范围限制为for循环。

我非常怀疑任何一种方式都可以提高效率。使用typedef也可以使其更具可读性。

typedef SomeClass::SomeContainer::iterator MyIter;

for (MyIter Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}

我建议使用较短的名字; - )

答案 3 :(得分:4)

不,在循环开始之前暂停iter.end()是个坏主意。如果循环更改容器,则结束迭代器可能无效。此外,end()方法保证为O(1)。

过早优化是万恶之源。

此外,编译器可能比您想象的更聪明。

答案 4 :(得分:4)

在g ++中观察了这个-O2优化(只是为了具体)

std :: vector,std :: list和std :: map(和朋友)的生成代码没有区别。 std :: deque的开销很小。

总的来说,从性能的角度来看,它没什么区别。

答案 5 :(得分:1)

我没有一种特别强烈的意见,尽管迭代器的生命周期会让我倾向于使用范围版本。

但是,可读性可能是一个问题;这可以通过使用typedef来帮助,因此迭代器类型更容易管理:

typedef SomeClass::SomeContainer::iterator sc_iter_t;

for (sc_iter_t Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}

不是一个巨大的改进,但有点。

答案 6 :(得分:1)

我没有任何控制台经验,但在大多数现代C ++编译器中,除了范围问题之外,任一选项最终都是等效的。 visual studio compilier几乎总是在调试代码中将条件比较放在一个隐式临时变量(通常是一个寄存器)中。因此,从逻辑上看,它看起来像是通过每次迭代进行的end()调用,优化的编译代码实际上只进行一次调用,并且比较是在循环中每个子时间完成的唯一事情。

在控制台上可能不是这种情况,但您可以解组循环以检查优化是否正在进行。如果是,那么你可以选择你喜欢的任何风格,或者是你组织中的标准风格。

答案 7 :(得分:1)

它可能会导致脱节代码,但我也想将它拉出到一个单独的函数,并将两个迭代器传递给它。

doStuff(coll.begin(), coll.end())

并且有..

template<typename InIt>
void doStuff(InIt first, InIt last)
{
   for (InIt curr = first; curr!= last; ++curr)
   {
       // Do stuff
   }
 }

喜欢的事情:

  • 永远不必提及丑陋的迭代器类型(或考虑它是const还是不是const)
  • 如果在每次迭代时都没有调用end(),我就会得到它

不喜欢的事情:

  • 分解代码
  • 额外功能调用的开销。

但是有一天,我们会有lambdas!

答案 8 :(得分:1)

我认为这根本不是坏事。只需使用typedef来避免STL详细程度和长行。

typedef set<Apple> AppleSet;
typedef AppleSet::iterator  AppleIter;
AppleSet  apples;

for (AppleIter it = apples.begin (); it != apples.end (); ++it)
{
   ...
}

Spartan Programming是缓解您的风格问题的一种方法。

答案 9 :(得分:0)

如果您关注范围,可以在初始化和循环周围抛出大括号。通常我要做的是在函数的开头声明迭代器并在整个程序中重用它们。

答案 10 :(得分:0)

我发现第二个选项更具可读性,因为你最终没有一条巨行。但是,Ferruccio对范围提出了一个很好的观点。

答案 11 :(得分:0)

我同意Ferruccio。某些人可能会首选这种样式,以便将end()调用拉出循环。

我可能还会补充一点,C ++ 0x实际上会使两个版本更清晰:

for (auto iter = container.begin(); iter != container.end(); ++iter)
{
   ...
}

auto iter = container.begin();
auto endIter = container.end();
for (; iter != endIter; ++iter)
{
   ...
}

答案 12 :(得分:0)

我通常会写:

SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
                                   IterEnd = m_SomeMemberContainerVar.end();

for(...)