循环内部的迭代程序初始化是否被认为是糟糕的风格,以及为什么



通常你会发现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行整洁。使用实际生产代码中的限定符和成员变量,如果使用第一个示例中的样式,则很容易使的行长。这就是为什么我在这个例子中故意设置了一个水平滚动条,这样你就可以看到我在说什么了。)

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

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

如果您正确地将代码换行,那么内联表单也同样可读。此外,您应该始终将iterEnd = container.end()作为优化:

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

更新:根据paercebal的建议修复了代码

另一种选择是使用foreach宏,例如boost foreach:

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

我知道宏在现代c++中是不受欢迎的,但在auto关键字广泛使用之前,这是我发现的获得简洁可读、完全打字安全快速的东西的最佳方法。您可以使用任何一种初始化样式来实现您的宏,从而获得更好的性能。

链接页面上还有一条关于将BOOST_FOREACH重新定义为FOREACH的注释,以避免烦人的全大写。

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

无论哪种方式,我都非常怀疑是否会提高效率。使用typedef还可以使其可读性更强。

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

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

在g++at-O2优化中研究了这一点(只是为了具体说明)

为std::vector、std::list和std::map(以及friends)生成的代码没有区别。std::deque有一个很小的开销。

所以总的来说,从性能的角度来看,这并没有什么不同。

不,在循环开始之前保持iter.end()是个坏主意。如果您的循环更改了容器,那么结束迭代器可能会无效。此外,保证了end()方法是O(1)。

过早的优化是万恶之源。

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

无论如何,我都没有特别强烈的意见,尽管迭代器的生存期会让我倾向于作用域版本。

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

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

不是很大的改善,但有一点。

我没有任何控制台经验,但在大多数现代C++编译器中,除了范围问题外,任何一个选项都是模棱两可的。visual studio编译器几乎总是在调试代码中将条件比较放在隐式临时变量(通常是寄存器)中。因此,虽然从逻辑上看,end()调用似乎是在每次迭代中进行的,但经过优化编译的代码实际上只进行一次调用,而比较是在循环的每个后续时间中唯一要做的事情。

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

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

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

并拥有。。

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

喜欢的东西:

  • 永远不要提及丑陋的迭代器类型(或者考虑它是否为const)
  • 如果在每次迭代中不调用end()有好处,我就会得到它

不喜欢的东西:

  • 分解代码
  • 附加函数调用的开销

但总有一天,我们会吃羊肉的!

我认为它一点也不坏。只需使用typedefs来避免STL的冗长和长行。

typedef set<Apple> AppleSet;
typedef AppleSet::iterator  AppleIter;
AppleSet  apples;
for (AppleIter it = apples.begin (); it != apples.end (); ++it)
{
   ...
}

斯巴达编程是一种减轻您的风格顾虑的方法。

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

我同意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)
{
   ...
}

我通常会写:

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

我发现第二个选项更可读,因为你不会得到一条巨大的线。然而,费鲁西奥提出了一个关于范围的好观点。

最新更新