[英]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_iterator
和iterator
没有共同的基类:
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.