Software |
DMX4Linux Driver Suite for Linux |
csv2iif.pl suite convert PayPal transactions to IIF, OFX, QIF |
Hardware |
DMX30 Interface 128Ch SPP |
DMX43 Interface 2out 2in EPP |
LED Hardware for Linux and Windows |
EPROM Sampler for 8 bits of sound |
Misc |
CatWeasel Linux drivers for MK3/4 PCI |
pg_trompe PostgreSQL replication |
trycatch C exception/signal handling lib |
Patches to various software |
Tools and small scripts |
Docs misc documents |
Links to lighting stuff |
C++ - erase from a map/set in a loopA nice property of a C++ Standard Template Library (STL) map, multimap, set or multiset is that you can insert or erase elements without invalidating existing iterators. This is especially useful if you are looping through a container and in the loop body decide if you want to erase the element currently processed. This article describes the pattern I prefer to write such a loop in the correct way. If you search for this problem you'll find lots of other styles to
write such a loop (for example
on stackoverflow),
but I don't like all answers I've found for three reasons:
Basic versionMy pattern to write such a loop avoids all three issues and in
additions looks almost like any other loop which iterates over
a STL container using a std::container c; for(std::container::iterator it=c.begin(), it_next=it; it!=c.end(); it=it_next) { ++it_next; ... } We are iterating over a container Note that erasing from a container while iterating over it, only work reliable with forward iterators! Now for a fully working example. I'll use a #include <cassert> #include <iostream> #include <map> using namespace std; int main() { typedef std::map<int,int> m_t; m_t m; m[0]=0; m[1]=1; m[2]=2; m[3]=3; m[4]=4; m[5]=5; m[6]=6; m[7]=7; m[8]=8; m[9]=9; for(m_t::iterator it=m.begin(), it_next=it; it!=m.end(); it=it_next) { ++it_next; cout << it->first << ' ' << it->second << endl; if(it->first & 1) m.erase(it); } cout << endl; assert(m.size() == 5); for(m_t::iterator it=m.begin(), it_next=it; it!=m.end(); it=it_next) { ++it_next; cout << it->first << ' ' << it->second << endl; m.erase(it); } assert(m.empty()); return 0; } The expected output of this program is: [doj@cubicle /tmp]$ c++ test.cpp && ./a.out 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 0 0 2 2 4 4 6 6 8 8 Improving the basic versionThe basic version can be improved, by using a third iterator to
store the value of std::container c; for(std::container::iterator it=c.begin(), it_next=it, it_end=c.end(); it != it_end; it = it_next) { ++it_next; ... } But what we still don't like is the std::container c; for(m_t::iterator it_end=c.end(), it_next=c.begin(), it = (it_next==it_end)?it_next:it_next++; it != it_next; it = (it_next==it_end)?it_next:it_next++) { ... } Because that is a lot of code to type every time you need such a
loop, you can use a define for convenience. In your own code you can
also use my foreach.hpp header file, which
contains the /** a for loop to iterate over a container. You can call erase() on i if the erase() function does not invalidate iterators different from i. This is true for STL map, multimap, set, multiset. @param c (STL) container, has to support begin() and end() methods. @param i name of iterator which is available in loop body */ #define foreach_e(c,i) for(auto end##i = (c).end(), next##i = (c).begin(), \ i = (next##i==end##i)?next##i:next##i++; \ i != next##i; \ it = (next##i==end##i)?next##i:next##i++) If you use g++
you need at version 4.4 or later to use The first loop of the test program would then look like: foreach_e(m, it) { cout << it->first << ' ' << it->second << endl; if(it->first & 1) m.erase(it); } cout << endl; assert(m.size() == 5); ContactFor questions and comments about this pattern write to Dirk Jagdmann <doj@cubic.org>. |