(c + +) error map delete operation and the underlying implementation mechanism of the container iterator in STL

Source: Internet
Author: User





1. Wrong map delete operation






Suppose there is a map container that stores the number of students in each home province in the university class, key is the province Chinese all spell, value is the number of students. Now you need to delete the number of 0 records, delete the code as follows:


map<string,int > countMap;for(map<string,int>::iterator it=countMap.begin();it!=countMap.end();++it)
{if(it->second==0)
{
        countMap.erase(it);
    }
}


Look, no problem, look carefully, there are giant pits, the removal of STL containers and insert operations hidden traps are mainly the following two.
(1) For the deletion of the node-type container (map, list, set) element, the insert operation will invalidate the iterator that points to the element, and the other element iterators are not affected;
(2) For the deletion of the sequential container (vector,string,deque) element, the insert operation will invalidate the iterator that points to the element and subsequent elements.



Therefore, there is no problem when deleting an element. That


for(map<string,int>::iterator it=countMap.begin();it!=countMap.end();++it)
{    if(it->second==0)
    {
        countMap.erase(it);        break;
    }
}


However, when you delete multiple elements, the program crashes. The reason is that when the specified element is deleted through an iterator, the iterator that points to that element is invalidated, and if the failed iterator is again done with the + + operation, it causes undefined behavior and the program crashes. The workaround is two, or in the case of the map container above, the correct implementation of the example delete operation:



method One: When you delete an element of a specific value, the iterator that holds the next element of the currently deleted element is saved before the element is deleted.


map<string,int >::iterator nextIt=countMap.begin();for(map<string,int>::iterator it=countMap.begin();;)
{    if(nextIt!=countMap.end())
    {
        ++nextIt;
    }    else
    { 
        break;
    }    if(it->second==0)
    {
        countMap.erase(it);
    }
    it=nextIt;
}


How to implement this method more concisely? The following is a detailed implementation of the effective STL book:


for(map<string,int>::iterator it=countMap.begin();it!=countMap.end();)
{    if(it->second==0)
    {
        countMap.erase(it++);
    }    else
    {
        ++it;
    }
}


This implementation takes advantage of the feature of the Post + + operator, which already points to the next element before the erase operation.



Furthermore, Map.erase () returns an iterator that points to the next element immediately following the deleted element, so it can be implemented as follows:


for(map<string,int>::iterator it=countMap.begin();it!=countMap.end();)
{    if(it->second==0)
    {
        it=countMap.erase(it);
    }   
    else
    {
        ++it;
    }
}


method Two: When you delete elements that meet certain conditions, you can use the Remove_copy_if & Swap method. By using the function template remove_copy_if the elements required by the conditional copy (copy) into the temporary container, the remaining elements that are not copied are the equivalent of being "removed" and then exchanging (swap) the elements in the two containers. You can call the map's member function swap directly. Reference code:


#include <iostream> #include <string> #include <map> #include <algorithm> #include <iterator> using namespace std; map <string, int> mapCount; // conditions not to copy bool notCopy (pair <string, int> key_value)
{return key_value.second == 0;
} int main ()
{
    mapCount.insert (make_pair ("tanwan", 0));
    mapCount.insert (make_pair ("anhui", 1));
    mapCount.insert (make_pair ("shanghai", 0));
    mapCount.insert (make_pair ("shandong", 1)); map <string, int> mapCountTemp; // temporary map container
    // The reason to use the iterator adapter inserter function template is because the element is inserted by calling the insert () member function, and the insertion position is specified by the user
    remove_copy_if (mapCount.begin (), mapCount.end (), inserter (mapCountTemp, mapCountTemp.begin ()), notCopy);

    mapCount.swap (mapCountTemp); // Achieve the exchange of two containers

    cout << mapCount.size () << endl; // output 2
    cout << mapCountTemp.size () << endl; // Output 4

    for (map <string, int> :: iterator it = mapCount.begin (); it! = mapCount.end (); ++ it)
    {cout << it-> first << "" << it-> second << endl;
    }
}


Program Output Result:


2
4
anhui 1
shandong 1


The disadvantage of this approach is that although the time complexity for the interchange of two maps is constant, the time overhead for copying is generally greater than the time overhead of deleting the specified element, and the temporary map container increases the overhead of the space.



The underlying implementation mechanism of the container iterator in 2.STL



Referring to the STL, it is necessary to immediately think of its main 6 components, namely: containers, iterators, algorithms, functor, adapters and spatial allocators, which is an important bridge to connect containers and algorithms.



The essence of a container iterator in an STL is a class object that acts like a cursor in a database, in addition to an iterator as a design pattern. We can increment it (or choose the next one) to access the elements in the container without knowing how it is implemented internally. It behaves much like a pointer, and can be used to access the specified element. But the two are quite different things, the pointer represents the memory address of the element, that is, where the object is stored in memory, and the iterator represents the relative position of the element in the container.



To customize an iterator, you overload the iterator with some basic operators: * (dereference), + + (self-increment), = = (equals),! = (not equal to), = (Assignment), so that it is used in the range for statement. Range for is a new statement in C++11, as we use the statement for (auto I:collection) on a collection, it actually means:


for (auto __begin = collection.begin (), auto __end = collection.end (); __ begin! = __ end; ++ __ begin)
{
     i = * __ begin; ... // loop body
}


Begin and end are the member functions of the collection, which returns an iterator. If you have a class that can have a range for operation, it must meet several of the following:
(1) Having the begin and end functions, which all return iterators, where the End function returns a value that points to the end of the collection but does not contain the end element, which is represented by the collection scope, and the range of an iterator is [begin, end] a left-closed right-open interval.
(2) must reload + +,! = The Reconciliation reference (*) operator. The iterator looks like a pointer, but is not a pointer. Iterators must be available through + + at the end of the meeting! = Condition so that the loop can be terminated.



The simplest implementation code is given below. We define a Cppcollection class with a string array that we can use to output each string with a range for.


class CPPCollection
{public:
     // class iterator class Iterator
     {public: int index; // element index
         CPPCollection & outer;
         Iterator (CPPCollection & o, int i): outer (o), index (i) () void operator ++ ()
         {
             index ++;
         }
         std :: string operator * () const
         {return outer.str [index];
         } bool operator! = (Iterator i)
         {return i.index! = index;
         }
     }; public: CPPCollection ()
     {string strTemp [10] = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}; int i = 0; for (auto strIt: strTemp)
         {
             str [i ++] = strIt;
         }
     }

     Iterator begin ()
     {return Iterator (* this, 0);
     }
     Iterator end ()
     {return Iterator (* this, 10);
     } private:
     std :: string str [10];
};


We defined an internal nested class iterator and overloaded it with + +, *,! = operator. Because the inner nested class in C + + is not associated with a peripheral class, in order to access the value of an external class object, we must pass in a reference (or pointer, in this case, an incoming reference). Iterator's self-increment method is actually to increase an internal index value. Judge! = is compared to another iterator, which is typically the end of the collection, and when our index value equals the end of the index value, the iterator is considered to have reached the end. In the Cppcollection class, Begin () and end () are defined to return to the beginning and ending iterators, respectively, and the following code is called:


 CPPCollection cpc;
  for (auto i : cpc)
  {
      std::cout <<i<<std::endl;
  }
  //or  CPPCollection cpc;
  for(CPPCollection::Iterator i= cpc.begin();i!=cpc.end();++i)
  {        std::cout<<*i<<std::endl;
   }


All the elements in the collection can be traversed.



In a generic algorithm, in order to operate on each element in the collection, we typically pass in the iterator header, the end of the iterator, and the predicate for the collection, for examplestd::find_if(vec.begin(),vec.end(),…), the generic algorithm is actually iterative at the first iteration of the iterator and then runs the corresponding behavior.



Reference documents



[1] Writing High-quality code: 150 recommendations for improving C + + programs. Li Jian Machinery industry press.



Related articles:



C # 2.0 specification (iterator) (ii)



Using iterators in C # to process wait Tasks _ basics



C # 2.0 specification (iterator) (i)


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.