【问题标题】:How best to control iteration direction?如何最好地控制迭代方向?
【发布时间】:2012-01-26 17:41:42
【问题描述】:

我有一个包含复制成本高昂的大型对象的容器。我有时必须正常迭代整个容器,有时反过来。一旦确定了迭代方向,我就不需要在飞行中更改,即不需要随机访问。

我希望做这样的事情:

#include <iostream>
#include <vector>
using namespace std;
int main( int argc, char** )
{
    // pretend this is a vector of expensive objects
    vector<int> foo = {1,2,3,4,5};

    // calculate forward or backward iteration direction
    bool backwards = (argc > 1);

    if( backwards )
        // prepare backward iteration, but don't copy objects
    else
        // prepare forward iteration, but don't copy objects

    for( auto& i : /* either forward or backward */ )
    {
        // my loop body
        cout << i;
    }

    return 0;
}

这是一个 C++11 程序,但我认为这对我没有帮助。我只是没有看到最好的方法来做到这一点。感谢您的帮助。

【问题讨论】:

    标签: c++ c++11 iteration


    【解决方案1】:

    C++ 标准容器带有这些称为“反向迭代器”的东西。使用std::vector::rbegin()std::vector::rend() 获得一个向后迭代向量的迭代器。 C++03 可以轻松做到这一点:

    #include <iostream> 
    #include <vector>  
    
    // Use const reference to pass expensive-to-copy types
    void loop_body(const int& i)
    {
        std::cout << i;
    }
    
    int main( int argc, char** ) 
    { 
        // pretend this is a vector of expensive objects 
        std::vector<int> foo = {1,2,3,4,5}; 
    
        // calculate forward or backward iteration direction 
        bool backwards = (argc > 1); 
    
        if( backwards ) { 
            std::for_each(foo.rbegin(), foo.rend(), &loop_body);
        } else { 
            std::for_each(foo.begin(), foo.end(), &loop_body);
        } 
        return 0; 
    } 
    

    您也许可以使用 C++11 中的 lambdas 来做到这一点:

    #include <iostream> 
    #include <vector> 
    
    int main( int argc, char** ) 
    { 
        // pretend this is a vector of expensive objects 
        std::vector<int> foo = {1,2,3,4,5}; 
    
        // calculate forward or backward iteration direction 
        bool backwards = (argc > 1); 
    
        // Use const reference to pass expensive-to-copy types
        auto loop_body = [](const int& i)
        {
            std::cout << i;
        };
    
        if( backwards ) { 
            std::for_each(foo.rbegin(), foo.rend(), loop_body);
        } else { 
            std::for_each(foo.begin(), foo.end(), loop_body);
        } 
        return 0; 
    }
    

    【讨论】:

    • 谢谢。从所有这些高质量的回复中选择答案是非常困难的。我选择了您的答案,因为它与我的示例代码非常匹配,并且还包含了分解我的循环体的关键建议。
    【解决方案2】:

    你为什么不把你的算法放到一个模板函数中呢?然后用 begin/end 或 rbegin/rend 调用它就很简单了。

    template <class Iterator>
    void do_stuff(Iterator first, Iterator last)
    {
        // Your loop code here.
    }
    

    或者您可以将 lambda(因为它是 C++11)与 std::for_each 一起使用:

    auto loop_body = [&](int &i) { std::cout << i << std::endl; } ;
    
    if (backward)
      std::for_each(v.rbegin(), v.rend(), loop_body);
    else
      std::for_each(v.begin(), v.end(), loop_body);
    

    【讨论】:

    • 不是最好的,因为您很可能可以使用 std lib 算法。那么你的函数只需要描述对单个元素进行的操作,而不是整个序列
    • @jalf :或者do_stuff 可以根据标准库算法来实现。我认为这是一个折腾。
    • @Mark:我编辑了答案,添加了 lambda 解决方案。希望你没事。
    • @jalf:我的意思是,这个想法是最好的。如您所见,我编辑了答案,添加了 lambda 和 std::for_each
    • 我曾假设循环需要某种状态,而不是像单个函数对象那样容易表示,但 lambda 解决方案也很好。
    【解决方案3】:

    标准库容器同时具有正常和反向迭代器,这解决了很大一部分问题。

    不幸的是,它们是不同的类型,因此您无法创建一个可以容纳正常或反向迭代器的变量。

    所以我要做的就是将你的循环包装在一个单独的函数中,并将其模板化以与两者一起使用:

    template <typename It>
    void myloop(It first, It last) {
        for(It cur = first; cur != last; ++cur)
        {
            // my loop body
            cout << *cur;
        }
    }
    

    然后这样称呼它:

    if( backwards )
        myloop(foo.rbegin(), foo.rend());
    else
        myloop(foo.begin(), foo.end());
    

    当然,您也可以使用标准库算法之一来代替循环:

    if( backwards )
        std::for_each(foo.rbegin(), foo.rend(), [](int item){  cout << item;});
    else
        std::for_each(foo.begin(), foo.end(), [](int item){  cout << item;});
    

    (请注意,为了简单起见,我在这里使用for_each。很可能,std::transformstd::copy 可能更适合描述您想要做什么。

    我还使用了 lambda 表达式而不是 myloop 函数。两者都可以,但 lambda 更短,而且 IMO 更易于阅读。

    【讨论】:

      【解决方案4】:
      std::vector<int>::iterator begin = foo.begin();
      std::vector<int>::iterator last = foo.end();
      if (last != begin)
      {
          --last;
          int direction = 1;
          if( backwards )
          {
              std::swap(begin, last);
              direction = -1;
          }
          for( auto& i = begin;  ; i += direction)
          {
              // do stuff
              if (i == last)
                  break;
          }
      }
      

      【讨论】:

      • 谢谢马克。这个答案紧跟示例代码的精神,并指出了使用单个 for 循环实例所需的旋转。
      【解决方案5】:

      即使这是一个老问题,我最近也遇到了同样的问题并以这种方式解决了:

      把它放在某处:

      namespace mani {
      
          // an extension to std::for_each where you can choose the direction:
          template <class InputIterator, class Function> Function for_each_reverse(bool reverse, const InputIterator& first, const InputIterator& last, Function fn)
          {
              if (reverse) {
                  return std::for_each(std::reverse_iterator<InputIterator>(last), std::reverse_iterator<InputIterator>(first), fn);
              }
              return std::for_each(first, last, fn);
          }
      
      }
      

      ...那么你就可以这么简单地使用它:

      auto start = ...
      auto end = ...
      bool reverse = ...
      mani::for_each_reverse(reverse, start, end, [&](const MyItem& item) {
          ...
      });
      

      如果reverse 为假,它将在正常方向上迭代项目。如果reverse 为真,它将向后迭代项目。

      【讨论】:

        【解决方案6】:

        您需要制作自己的迭代器包装器。我可以想到三种方法:

        • 一个类似于union 的两个迭代器类型;它有两个不同类型的迭代器成员,以及一个选择哪一个处于活动状态的标志。

        • 一个包含单个双向迭代器和一个指示是正向还是反向操作的标志。

        • 某种带有虚函数的通用迭代器。 (效率低,但写起来可能很简单)

        【讨论】:

        • 也就是说,“将代码编写为在迭代器范围上运行的单独函数”方法可能比您尝试采用的方法更好。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-05-22
        • 1970-01-01
        • 2015-02-18
        • 2012-11-14
        • 1970-01-01
        • 2017-07-09
        • 2012-02-03
        相关资源
        最近更新 更多