简体   繁体   English

C++ STL:由于缺少迭代器和反向迭代器的基类而导致重复代码

[英]C++ STL: Duplicating code due to missing base-class for iterator and reverse_iterator

In my current C++-project I have an STL map which maps integer keys onto objects.在我当前的 C++ 项目中,我有一个 STL 映射,它将整数键映射到对象上。 An algorithm returns a set of entries.算法返回一组条目。 The returned data depends on the algorithm's input and hence cannot be predicted:返回的数据取决于算法的输入,因此无法预测:

  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]);
        }
     }
  }

As mentioned in the example I can replace map::count() and operator[] -calls with find.如示例中所述,我可以用 find 替换map::count()operator[]调用。 The STL-reference says that map::find()'s complexity is logarithmic in size ( O(log n) ). STL-reference说 map::find() 的复杂性在大小上是对数的( O(log n) )。

I discovered that in most cases the entries in myMap are very close for two sequent entries in the result.我发现在大多数情况下,myMap 中的条目与结果中的两个连续条目非常接近。 Therefore I came to the conclusion that I would achieve better performance if I replaced the map.find() calls by iterators:因此我得出的结论是,如果我用迭代器替换 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);
        }
     }

This concept works but I have a big problem: Depending on the inputData, I have to search forward or backward.这个概念有效,但我有一个大问题:根据 inputData,我必须向前或向后搜索。 Consider that I call the code inside main() multiple times and the inputData changes for these calls.考虑到我多次调用main()的代码,并且这些调用的 inputData 发生了变化。 Instead of checking whether to increment or decrement the iterator inside the while -loop, I could decide that before entering the for -loop.我可以在进入for循环之前决定, while不是检查是在while循环内递增还是递减迭代器。

I thought that I'm fine with just switching the map<>::iterator to map<>::reverse_iterator and using rbegin() / rend() instead of begin() / end() .我认为只需将map<>::iterator切换到map<>::reverse_iterator并使用rbegin() / rend()而不是begin() / end() But then I realized that reverse_iterator and iterator have no common base class:但后来我意识到reverse_iteratoriterator没有共同的基类:

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

That would be great, but there is no base_iterator .那会很棒,但没有base_iterator

I know a simple workaround for this problem: I just need to copy the whole for -loop and adjust it for both cases:我知道这个问题的简单解决方法:我只需要复制整个for循环并针对这两种情况进行调整:

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

Very bad ... Do you know a better solution?非常糟糕......你知道更好的解决方案吗?

A common base type is unnecessary when the language allows Generic Programming.当语言允许泛型编程时,不需要公共基类型。

What you simply need to realize is that instead of having a long-winded linear functions with several choices along the way, you can have several nested function in which each choice lead to a different call.您只需要意识到的是,您可以拥有多个嵌套函数,其中每个选择都会导致不同的调用,而不是冗长的线性函数和多个选项。

Taking your example:以你的例子为例:

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

You can transform the code into the following:您可以将代码转换为以下内容:

// 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
}

And then inject the call straight into the if/else body:然后调用直接注入if/else主体:

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

And one good thing is that you reduce the number of changes of states within your functions and thus their complexity.一件好事是你减少了函数中状态变化的数量,从而减少了它们的复杂性。

Use a templated function.使用模板化函数。 The only place in the Standard library where inheritance is used over templates is IOstreams, as far as I'm aware (and that was a mistake).标准库中唯一在模板上使用继承的地方是 IOstreams,据我所知(这是一个错误)。

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

However, I question if you would simply be better off changing to an always O(1) container, like an unordered_map .但是,我质疑您是否最好更改为始终为 O(1) 的容器,例如unordered_map

You could use templates:您可以使用模板:

 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());
 }

From c++14 if you don't want to write template<...> you can let compiler do it for you and use lambda instead create function template.从 c++14 开始,如果您不想编写template<...>您可以让编译器为您完成并使用 lambda 代替创建函数模板。

then call would be like that:然后调用将是这样的:

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());                                                                                                                                  
    }                                                                                                                                                                                          
  }              

With c++20 we will be probably able to do same with std::ranges.使用 c++20,我们可能可以对 std::ranges 做同样的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM