【问题标题】:Is Iterator initialization inside for loop considered bad style, and why?循环内的迭代器初始化是否被认为是不好的风格,为什么?
【发布时间】:2010-09-16 09:16:28
【问题描述】:

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

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

您对此有何看法?除了可能限制 Iter 的范围之外,第一种风格是否有任何专业人士?

【问题讨论】:

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


    【解决方案1】:

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

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

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

    【讨论】:

    • 对。 C++0x 将引入新修改的关键字 auto,这将使我们能够避免迭代器声明。请注意,我猜您的代码不会因为第二个“SomeClass::SomeContainer::iterator”声明而编译。它应该是 for(m::iterator i =..., iEnd = ...
    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

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

      我严重怀疑无论哪种方式都可以提高效率。也可以使用 typedef 使其更具可读性。

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

      我会推荐更短的名字 ;-)

      【讨论】:

      • 不一定,在我的环境中这是合法有效的:“for(int i = 0; i
      • @Ferrucio:这不是有效的 C++ - 你可能使用的是旧版本的 VC++,它不符合标准,但你必须在升级时修复它
      • 没错,旧的编译器可能仍然接受旧的标准。使用 VC++,您实际上可以选择打开/关闭它。
      • 在每次迭代中调用 end() 可能会对性能产生影响,具体取决于容器的实现方式。
      • @steffenj,在您的环境中“工作”的代码不是标准的 c++(直到 2008 年,Visual C++ 对此都很糟糕)
      【解决方案4】:

      在 g++ 中查看了这个 -O2 优化(只是为了具体一点)

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

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

      【讨论】:

        【解决方案5】:

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

        过早的优化是万恶之源。

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

        【讨论】:

        • 如果你的循环改变了容器,那么递增的迭代器也会失效,所以如果你使用这种类型的“foreach”循环,你最好预先初始化结束。
        • 不,您可以通过将返回的迭代器从无效函数中保存下来,始终保持计数器迭代器的最新状态。事实上,这就是你应该做的,也是这些返回值的原因。但是世界上没有理由预先保存结束迭代器。
        【解决方案6】:

        我没有特别强烈的意见,尽管迭代器生命周期会使我倾向于 for-scoped 版本。

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

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

        不是很大的改进,但有点。

        【讨论】:

        • 好主意。我自己用过几次,但最近还不足以记得评论它。它使代码更加更具可读性。
        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

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

          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!

          【讨论】:

            【解决方案9】:

            我不认为这是一种糟糕的风格。只需使用 typedef 即可避免 STL 冗长和冗长的行。

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

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

            【讨论】:

              【解决方案10】:

              如果您担心范围,可以在初始化和循环周围加上大括号。通常我会在函数的开头声明迭代器并在整个程序中重用它们。

              【讨论】:

                【解决方案11】:

                我同意费鲁乔的观点。有些人可能更喜欢第一种样式,以便将 end() 调用拉出循环。

                我还可以补充一点,C++0x 实际上会使这两个版本都更加简洁:

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

                【讨论】:

                • 这样更好!你可以说 for (auto iter : container) { }
                • 那么整个对话将毫无意义。万岁!
                【解决方案12】:

                我通常会写:

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

                【讨论】:

                  【解决方案13】:

                  我发现第二个选项更具可读性,因为您最终不会得到一条大线。然而,Ferruccio 提出了一个关于范围的好观点。

                  【讨论】:

                  • 你的意思是第二个选项,而不是第一个?
                  猜你喜欢
                  • 1970-01-01
                  • 2010-10-18
                  • 1970-01-01
                  • 2011-12-17
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-05-07
                  • 1970-01-01
                  相关资源
                  最近更新 更多