代码之家  ›  专栏  ›  技术社区  ›  steffenj

for循环内部的迭代器初始化是否被认为是错误的样式,为什么?

  •  11
  • steffenj  · 技术社区  · 17 年前

    通常,您会发现如下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的范围之外,第一种风格是否有其他的专业风格?

    13 回复  |  直到 17 年前
        1
  •  13
  •   Ates Goral    17 年前

    如果将代码正确地包装成行,那么内联表单的可读性将相同。另外,你应该一直这样做 iterEnd = container.end() 作为优化:

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

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

        2
  •  9
  •   joeld    17 年前

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

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

    我知道在现代C++中,宏是被禁止的,但是直到AutoTi关键字被广泛地使用,这才是我找到的最好的方法,它是简洁易读的,而且仍然是完全类型化的和快速的。您可以使用任何使您获得更好性能的初始化样式来实现宏。

    链接页面上还有一条关于将boost_foreach重新定义为foreach的注释,以避免烦人的所有大写字母。

        3
  •  5
  •   Ferruccio    17 年前

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

    我很怀疑这两种方法都能提高效率。也可以使用typedef使其更易于阅读。

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

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

        4
  •  4
  •   Chris Jefferson    17 年前

    在g++at-o2优化中看到了这一点(只是具体地说)

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

    所以一般来说,从性能的角度来看,它几乎没有什么区别。

        5
  •  4
  •   wilhelmtell    17 年前

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

    过早的优化是万恶之源。

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

        6
  •  1
  •   Michael Burr    17 年前

    虽然迭代器的生命周期将使我倾向于for-scoped版本,但是我并没有一个特别强烈的观点。

    但是,可读性可能是一个问题;这可以通过使用typedef得到帮助,这样迭代器类型更易于管理:

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

    不是很大的进步,但有一点。

        7
  •  1
  •   user25601    17 年前

    我没有任何控制台经验,但是在大多数现代C++编译器中,除了范围问题之外,两个选项都是等价的。实际上,即使在调试代码中,Visual Studio编译器也会将条件比较放在隐式临时变量(通常是寄存器)中。因此,虽然从逻辑上看,end()调用似乎是通过每次迭代进行的,但优化编译的代码实际上只进行一次调用,而比较是通过循环在每个子队列时间内进行的唯一操作。

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

        8
  •  1
  •   JohnMcG    17 年前

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

    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()会有收益,我会得到它

    不喜欢的事情:

    • 破解密码
    • 额外函数调用的开销。

    但总有一天,我们会有羔羊!

        9
  •  1
  •   An̲̳̳drew Chad Okere    17 年前

    我觉得它一点也不坏。只需使用typedef来避免stl冗长和长行。

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

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

        10
  •  0
  •   Paul Nathan    17 年前

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

        11
  •  0
  •   Fred Larson    17 年前

    我同意费鲁奇奥的观点。为了将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
  •   Nemanja Trifunovic    17 年前

    我通常会写:

    SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
                                       IterEnd = m_SomeMemberContainerVar.end();
    
    for(...)
    
        13
  •  0
  •   Herms    17 年前

    我发现第二个选项更具可读性,因为你不会以一条巨线结束。然而,Ferroccio提出了一个关于范围的好观点。