【问题标题】:C++ STL: Duplicating code due to missing base-class for iterator and reverse_iteratorC++ STL:由于缺少 iterator 和 reverse_iterator 的基类而导致重复代码
【发布时间】:2012-03-04 22:39:21
【问题描述】:

在我当前的 C++ 项目中,我有一个 STL 映射,它将整数键映射到对象上。算法返回一组条目。返回的数据取决于算法的输入,因此无法预测:

  class MyClass
  {
     //...
  };

  int myAlgorithm(vector<int>::iterator inputIt)
  {
     // return a key for myMap which is calculated by the current value of inputData
  }

  int main(int argc, char *argv[])
  {
     vector<int> inputData;
     map<int, MyClass> myMap;
     //<fill map with some data>
     //<fill inputData>

     vector<MyClass> result;

     for (vector<int>::iterator it = inputData.begin(); it != inputData.end(); it++)
     {
        int myMapKey = myAlgorithm(*it);
        // count() > 0 means "check whether element exists. Performance can be improved by replacing
        //    the operator[] and count() calls by map::find(). However, I want to simplify things
        //    in this example.
        if (myMap.count(myMapKey) > 0)
        {
           // in some cases there is no entry in myMap
           result.push_back(myMap[myMapKey]);
        }
     }
  }

如示例中所述,我可以用 find 替换 map::count()operator[]-calls。 STL-reference 表示 map::find() 的复杂度是对数大小 (O(log n))。

我发现在大多数情况下,myMap 中的条目对于结果中的两个后续条目非常接近。因此,我得出的结论是,如果我用迭代器替换 map.find() 调用,我将获得更好的性能:

     map<int, MyClass>::iterator myMapIt = myMap.begin();
     for (vector<int>::iterator it = inputData.begin(); it != inputData.end(); it++)
     {
        int myMapKey = myAlgorithm(*it);
        // just increment iterator
        while (myMapKey != myMapIt->first)
        {
           myMapIt++;
           // we didn't find anything for the current input data
           if (myMapIt == myMap::end() || myMapIt->first > myMapKey)
           {
              break;
           }
        }

        // I know that I'm checking this twice, but that's not the point of my
        //    question ;)
        if (myMapIt == myMap::end() || myMapIt->first > myMapKey)
        {
           // probably it would be better to move the iterator back to the position
           //    where we started searching, to improve performance for the next entry
           myMapIt = myMap.begin();
        }
        else
        {
           result.push_back(myMapIt.second);
        }
     }

这个概念可行,但我有一个大问题:根据 inputData,我必须向前或向后搜索。考虑到我多次调用main() 中的代码,并且这些调用的 inputData 发生了变化。我可以在进入for-loop 之前决定是否增加或减少while-loop 中的迭代器。

我认为只需将map&lt;&gt;::iterator 切换为map&lt;&gt;::reverse_iterator 并使用rbegin()/rend() 而不是begin()/end() 就可以了。但后来我意识到reverse_iteratoriterator 没有共同的基类:

     map<int, MyClass>::base_iterator myIt;
     if (/* ... */)
     {
        myMapIt = myMap::begin();
        myMapEndIt = myMap::end();
     }
     else
     {
        myMapIt = myMap::rbegin();
        myMapEndIt = myMap::rend();
     }
     /* for (...) ... */

那太好了,但是没有base_iterator

我知道这个问题的简单解决方法:我只需要复制整个 for-loop 并针对这两种情况进行调整:

     if (/* ... */)
     {
        /* for(...) which uses normal iterator in the while-loop */
     }
     else
     {
        /* for(...) which uses reverse iterator in the while-loop */
     }

非常糟糕...您知道更好的解决方案吗?

【问题讨论】:

  • 调用函数模板会起作用吗?
  • 您是如何得出结论的,即您将获得更好的性能?你有数据备份吗?如果这不是您的应用程序中的真正瓶颈,您可能只是为自己做更多的工作。也就是说,这仍然是一个有趣的问题。 :)
  • 由于使用 map::find() 时的 O(log n) 复杂性,无法假设下一个条目接近当前条目。这段代码处于非常关键的位置,在几个嵌套循环中

标签: c++ stl map iterator polymorphism


【解决方案1】:

你可以使用模板:

 template <typename T>
 void myFunction(T start, T end)
 {
     /* for (...) ... */
 }

 map<int, MyClass>::base_iterator myIt;
 if (/* ... */)
 {
    myFunction(myMap.begin(), myMap.end());
 }
 else
 {
    myFunction(myMap.rbegin(), myMap.rend());
 }

【讨论】:

    【解决方案2】:

    使用模板函数。据我所知,标准库中唯一在模板上使用继承的地方是 IOstreams(这是一个错误)。

    template<typename Iterator> ... stuff(Iterator begin, Iterator end) {
        // implement loop here
    }
    if (/*...*/) {
        stuff(map.rbegin(), map.rend());
    } else {
        stuff(map.begin(), map.end());
    }
    

    但是,我怀疑您是否会更好地更改为始终为 O(1) 的容器,例如 unordered_map

    【讨论】:

    • 你有更多关于 unordered_map 的信息吗? 1. 我无法想象它是如何工作的 2. 我读到它的复杂性并不稳定,在最坏的情况下也可能是 O(n)
    【解决方案3】:

    当语言允许泛型编程时,不需要通用的基类型。

    您只需要意识到,您可以拥有多个嵌套函数,其中每个选择都会导致不同的调用,而不是具有多个选择的冗长线性函数。

    以你为例:

    boost::any_iterator start, end;
    if (/* ... */) {
      start = map.begin(), end = map.end();
    } else {
      start = map.rbegin(), end = map.rend();
    }
    
    // do something with start and end
    

    您可以将代码转换为以下内容:

    // Define a free-function in the .cpp to help factor common stuff
    template <typename FwdIt>
    static void dosomething(FwdIt start, FwdIt end) {
      // do something with start and end
    }
    

    然后将调用直接注入if/else正文中:

    if (/* ... */) {
      dosomething(map.begin(), map.end());
    } else {
      dosomething(map.rbegin(), map.rend());
    }
    

    还有一件好事是减少了函数中状态变化的次数,从而降低了它们的复杂性。

    【讨论】:

    • 我试过了,但我想知道为什么我的整个算法现在慢了 5 倍。 dosomething 是内联的吗?我找不到任何理由说明我的想法会导致性能下降
    • 这只是我实现中的一个错误,现在它更快了
    【解决方案4】:

    从c++14开始,如果你不想写template&lt;...&gt;,你可以让编译器为你做,用lambda代替创建函数模板。

    然后调用会是这样的:

    void your_function(auto &some_container, bool from_front) {                                                                                                                                  
        auto setter = [&](auto begin, auto end) {                                                                                                                                                  
          auto no_of_elements_to_change = 3;                                                                                                                                                       
          for (auto el = begin; el != end; ++el) {                                                                                                                                                 
            *el = +1000;  /// stuff you want to do with last 3 elements                                                                                                                            
            if (--no_of_elements_to_change == 0) {                                                                                                                                                 
              break;                                                                                                                                                                               
            }                                                                                                                                                                                      
          }                                                                                                                                                                                        
        };                                                                                                                                                                                         
        if (from_front) {                                                                                                                                                                          
          setter(some_container.begin(), some_container.end());                                                                                                                                    
        } else {                                                                                                                                                                                   
          setter(some_container.rbegin(), some_container.rend());                                                                                                                                  
        }                                                                                                                                                                                          
      }              
    

    使用 c++20,我们可能可以使用 std::ranges 做同样的事情。

    【讨论】:

      猜你喜欢
      • 2022-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-07
      • 2018-04-23
      • 2016-07-25
      相关资源
      最近更新 更多