1. Introduction to iterator
In order to improve the efficiency of C + + programming, STL (Standard Template Library) provides many containers, including vector, list, map, set, etc. However, some containers (vectors) can access the data in the container by subscript index, but most containers (list, map, set) cannot access the elements in the container in this way. In order to unify the access methods when accessing different containers, STL designs an embedded iterator class for each container when it is implemented. Different containers have their own exclusive iterators (the exclusive iterators are responsible for implementing the specific details of the corresponding container access elements), and iterators are used to access the data in the containers. In addition, we can combine the container with the general algorithm through the iterator. As long as we give different iterators to different algorithms, we can perform the same operation on different containers, such as find lookup function (because the iterator provides a unified access method, which is the benefit of using the iterator). The iterator overloads some basic operations such as *, - >, + +, = =,! =, = to make it have the ability to traverse complex data structures. Its traversal mechanism depends on the traversed container. The use of all iterators is very similar to the use of pointers. The header and tail iterators of the container are obtained by the begin and end functions. The end iterator is not included in the container. When the iterators returned by the begin and end are the same, the container is empty.
STL is mainly composed of container, iterator, algorithm, function object and memory allocator.
2. Implementation principle of iterator
First, let's look at the implementation of iterators in STL:
As can be seen from the above figure, STL achieves external unification through type aliasing. In different containers, the real iterator types of type aliases are different, and the real iterator types are different for the basic operations such as + +, --, *, - >. (PS: iterator well explains the significance of separation of interface and Implementation)
Now that we know how to implement the iterator, how can we design a simple iterator of the list container?
- list class needs a method to operate iterator
- begin/end
- insert/erase/emplace
- The list class has an internal class list iterator
- There is a member variable ptr pointing to an element in the list container
- iterator is responsible for basic operations such as overloading + +, --, *, - >
- The list class defines the type alias of the internal class list iterator
These are the details that need to be considered to implement a simple iterator of a list container.
3. Simple implementation of iterator
My list. H
// // Created by wengle on 2020-03-14. // #ifndef CPP_PRIMER_MY_LIST_H #define CPP_PRIMER_MY_LIST_H #include <iostream> template<typename T> class node { public: T value; node *next; node() : next(nullptr) {} node(T val, node *p = nullptr) : value(val), next(p) {} }; template<typename T> class my_list { private: node<T> *head; node<T> *tail; int size; private: class list_iterator { private: node<T> *ptr; //Pointer to an element in the list container public: list_iterator(node<T> *p = nullptr) : ptr(p) {} //Overload + +, --, *, - > and other basic operations //Return reference, convenient to modify the object through * it T &operator*() const { return ptr->value; } node<T> *operator->() const { return ptr; } list_iterator &operator++() { ptr = ptr->next; return *this; } list_iterator operator++(int) { node<T> *tmp = ptr; // This is a constant pointer to the list iterator, so * this is the list iterator object, and the preceding + + has been overloaded ++(*this); return list_iterator(tmp); } bool operator==(const list_iterator &t) const { return t.ptr == this->ptr; } bool operator!=(const list_iterator &t) const { return t.ptr != this->ptr; } }; public: typedef list_iterator iterator; //Type alias my_list() { head = nullptr; tail = nullptr; size = 0; } //Insert elements from the end of the list void push_back(const T &value) { if (head == nullptr) { head = new node<T>(value); tail = head; } else { tail->next = new node<T>(value); tail = tail->next; } size++; } //Print linked list elements void print(std::ostream &os = std::cout) const { for (node<T> *ptr = head; ptr != tail->next; ptr = ptr->next) os << ptr->value << std::endl; } public: //How to operate iterators //Return to the head pointer of the list iterator begin() const { return list_iterator(head); } //Return to the end pointer of the list iterator end() const { return list_iterator(tail->next); } //Other member functions insert / erase / replace }; #endif //CPP_PRIMER_MY_LIST_H
test.cpp
// // Created by wengle on 2020-03-14. // #include <string> #include "my_list.h" struct student { std::string name; int age; student(std::string n, int a) : name(n), age(a) {} //Overload output operator friend std::ostream &operator<<(std::ostream &os, const student &stu) { os << stu.name << " " << stu.age; return os; } }; int main() { my_list<student> l; l.push_back(student("bob", 1)); //Passing a temporary quantity as an argument to the push back method l.push_back(student("allen", 2)); l.push_back(student("anna", 3)); l.print(); for (my_list<student>::iterator it = l.begin(); it != l.end(); it++) { std::cout << *it << std::endl; *it = student("wengle", 18); } return 0; }
4. Iterator failure
// inserting into a vector #include <iostream> #include <vector> int main () { std::vector<int> myvector (3,100); std::vector<int>::iterator it; it = myvector.begin(); it = myvector.insert ( it , 200 ); myvector.insert (it,200,300); //it = myvector.insert (it,200,300); myvector.insert (it,5,500); //When the program is executed here, it will crash for (std::vector<int>::iterator it2=myvector.begin(); it2<myvector.end(); it2++) std::cout << ' ' << *it2; std::cout << '\n'; return 0; }
The above code well shows what is iterator failure? What are the problems caused by iterator failure?
After executing myvector.insert (it, 200300); in fact, MyVector has applied for a new memory space to store the previously saved data and the data inserted this time. Since the pointer inside the IT iterator still points to the element of the old memory space, once the old memory space is released, execute myvector.insert (it, 5500); PS: because you are operating a piece of memory that has been released through the pointer of iterator, most of the time, it will crash. Iterator failure means that the pointer inside the iterator does not update in time and still points to the old memory space element.
The figure above shows the implementation of vector container insert method in STL source code. When the number of inserted elements exceeds the remaining capacity of the current container, the iterator will be invalidated. This is also the reason why myvector.insert (it, 200300) and 200 elements are inserted in the test code. In order to simulate a scenario that exceeds the remaining capacity of the current container, if your test environment does not have crash, you can set more elements to be inserted.