Class Structure Framework
vector inherits from _Vector_val, from _Container_base, and _Container_base is defined in debug mode (only exploring debug mode); typedef_Container_base 12_Container_base; the most important thing here is to define a member variable called Container Agent _Container_proxy*_Myproxy; this "generation" Li"means to act as an agent between container and iterator.
// CLASS _Container_proxy struct _Container_proxy { // store head of iterator chain and back pointer _Container_proxy() : _Mycont(0), _Myfirstiter(0) { // construct from pointers } const _Container_base12 *_Mycont;//Point to the container, which actually points to our vector. _Iterator_base12 *_Myfirstiter; //Point to the first iterator };
So let's start with _Container_base 12.
struct _CRTIMP2_PURE _Container_base12 { // store pointer to _Container_proxy public: _Container_base12() : _Myproxy(0) { // construct childless container } _Container_base12(const _Container_base12&) : _Myproxy(0) { // copy a container } _Container_base12& operator=(const _Container_base12&) { // assign a container return (*this); } ~_Container_base12() { // destroy the container _Orphan_all(); } _Iterator_base12 **_Getpfirst() const { // get address of iterator chain return (_Myproxy == 0 ? 0 : &_Myproxy->_Myfirstiter); } void _Orphan_all() // orphan all iterators { // orphan all iterators #if _ITERATOR_DEBUG_LEVEL == 2 if (_Myproxy != 0) { // proxy allocated, drain it _Lockit _Lock(_LOCK_DEBUG); //The next two steps are to disconnect your container from all iterator s that are related to you. for (_Iterator_base12 **_Pnext = &_Myproxy->_Myfirstiter; *_Pnext != 0; *_Pnext = (*_Pnext)->_Mynextiter) (*_Pnext)->_Myproxy = 0;//1. Set the proxy of all iterator s to 0 _Myproxy->_Myfirstiter = 0;//2. Set the first iterator pointer in the list to 0 } #endif /* _ITERATOR_DEBUG_LEVEL == 2 */ } void _Swap_all(_Container_base12&); // swap all iterators _Container_proxy *_Myproxy; };
_ The most important variables in the Vector_val class should be easy to understand. For example, if a vector allocates ten ints of memory, but only the first three are assigned values, then _Myfirst points to element 0, _Mylast points to element 3, and _Myend points to element 10 (which does not exist). _ Alval is a memory allocator, which basically means replacing new.
// TEMPLATE CLASS _Vector_val template<class _Ty, class _Alloc> class _Vector_val : public _Container_base { // base class for vector to hold data public: typedef typename _Alloc::template rebind<_Ty>::other _Alty; typedef typename _Alty::size_type size_type; typedef typename _Alty::difference_type difference_type; typedef typename _Alty::pointer pointer; typedef typename _Alty::const_pointer const_pointer; typedef typename _Alty::reference reference; typedef typename _Alty::const_reference const_reference; typedef typename _Alty::value_type value_type; pointer _Myfirst; // pointer to beginning of array pointer _Mylast; // pointer to current end of sequence pointer _Myend; // pointer to end of array _Alty _Alval; // allocator object for values };
// TEMPLATE CLASS vector template<class _Ty, class _Ax = allocator<_Ty> > class vector : public _Vector_val<_Ty, _Ax> { // varying size array of values public: typedef vector<_Ty, _Ax> _Myt; typedef _Vector_val<_Ty, _Ax> _Mybase; typedef typename _Mybase::_Alty _Alloc; typedef _Alloc allocator_type; typedef typename _Alloc::size_type size_type; typedef typename _Alloc::difference_type difference_type; typedef typename _Alloc::pointer pointer; typedef typename _Alloc::const_pointer const_pointer; typedef typename _Alloc::reference reference; typedef typename _Alloc::const_reference const_reference; typedef typename _Alloc::value_type value_type; #define _VICONT(it) it._Getcont() #define _VIPTR(it) (it)._Ptr typedef _Vector_iterator<_Mybase> iterator; typedef _Vector_const_iterator<_Mybase> const_iterator; typedef _STD reverse_iterator<iterator> reverse_iterator; typedef _STD reverse_iterator<const_iterator> const_reverse_iterator; }
Starting from the Construction of vector
std::vector<int> vecInt;
vector() : _Mybase() { // construct empty vector }
_Vector_val(_Alloc _Al = _Alloc()) : _Alval(_Al) { // construct allocator from _Al //Define an allocator of type _Container_proxy typename _Alloc::template rebind<_Container_proxy>::other _Alproxy(_Alval); //These lines of code are equivalent to: _Myproxy = new_Container_proxy(); this->_Myproxy = _Alproxy.allocate(1); _Cons_val(_Alproxy, this->_Myproxy, _Container_proxy()); this->_Myproxy->_Mycont = this; //Initialize three super-important members _Myfirst = 0; _Mylast = 0; _Myend = 0; }
There is also a line of code in the xmemory file: define ALLOCATOR allocator, so the memory allocator we use is ALLOCATOR. Why not use ALLOCATOR directly? The naming method of at the beginning of means that it is internal. It may be related to this.
View internal implementation details from push_back
vecInt.push_back(1);
void push_back(_Ty&& _Val) { // insert element at end //If the address of _Val is between this - > Mylast and this - > Myfirst if (_Inside(_STD addressof(_Val))) { // push back an element size_type _Idx = _STD addressof(_Val) - this->_Myfirst; // if (this->_Mylast == this->_Myend) _Reserve(1); _Orphan_range(this->_Mylast, this->_Mylast); //In contrast to the else block, the _Myfirst[_Idx] assignment is used here. _Cons_val(this->_Alval, this->_Mylast, _STD forward<_Ty>(this->_Myfirst[_Idx])); ++this->_Mylast; } else { // push back a non-element //If there is no space, it needs to be expanded and capacity will become larger. if (this->_Mylast == this->_Myend) _Reserve(1);//Extend 1, expand an element _Orphan_range(this->_Mylast, this->_Mylast); //Assign _Val to the last _Mylast position and move _Mylast one bit backwards _Cons_val(this->_Alval, this->_Mylast, _STD forward<_Ty>(_Val)); ++this->_Mylast; } }
Look at the expansion from Extension 1 above
void _Reserve(size_type _Count) { // ensure room for _Count new elements, grow exponentially size_type _Size = size(); //If the expansion of _Count exceeds max_size(), the error will be reported. if (max_size() - _Count < _Size) _Xlen(); //If you don't need expansion, do nothing; in fact, you don't need to call the _Reserve function at this time. else if ((_Size += _Count) <= capacity()) ; else reserve(_Grow_to(_Size)); }
_ Reserve calls reserve. Size_type_Grow_to(size_type_Count) const.
size_type _Grow_to(size_type _Count) const { // grow by 50% or at least to _Count size_type _Capacity = capacity(); _Capacity = max_size() - _Capacity / 2 < _Capacity ? 0 : _Capacity + _Capacity / 2; // try to grow by 50% //This function returns the large between capacity ()* 1.5 and _Count if (_Capacity < _Count) _Capacity = _Count; return (_Capacity); }
void reserve(size_type _Count) { // determine new minimum length of allocated storage if (max_size() < _Count) _Xlen(); // result too long else if (capacity() < _Count) { // not enough room, reallocate //1. Reallocate memory pointer _Ptr = this->_Alval.allocate(_Count); _TRY_BEGIN //2. Copy the contents of the previous memory _Umove(this->_Myfirst, this->_Mylast, _Ptr); _CATCH_ALL //3. Delete memory before catch arrives this->_Alval.deallocate(_Ptr, _Count); _RERAISE; _CATCH_END size_type _Size = size(); if (this->_Myfirst != 0) { // destroy and deallocate old array //4. Destruction of these objects, if it's type int, it's not destructive. _Destroy(this->_Myfirst, this->_Mylast); //3. Delete memory before catch arrives this->_Alval.deallocate(this->_Myfirst, this->_Myend - this->_Myfirst); } //5,_Orphan_all() is to set all iteraotor's _Myproxy to 0 and _Myproxy->_Myfirstiter = 0; this->_Orphan_all(); //6. Reassign the location variable this->_Myend = _Ptr + _Count;//The object pointed at does not exist. this->_Mylast = _Ptr + _Size;//Point to the element behind the real last element this->_Myfirst = _Ptr;//Always point to element 0 } }
iterator
struct _Iterator_base12 { // store links to container proxy, next iterator public: _Iterator_base12() : _Myproxy(0), _Mynextiter(0) { // construct orphaned iterator } _Iterator_base12(const _Iterator_base12& _Right) : _Myproxy(0), _Mynextiter(0) { // copy an iterator *this = _Right; } _Iterator_base12& operator=(const _Iterator_base12& _Right) { // assign an iterator if (_Myproxy != _Right._Myproxy) _Adopt(_Right._Myproxy->_Mycont); return (*this); } ~_Iterator_base12() { // destroy the iterator #if _ITERATOR_DEBUG_LEVEL == 2 _Lockit _Lock(_LOCK_DEBUG); _Orphan_me(); #endif /* _ITERATOR_DEBUG_LEVEL == 2 */ } //_ The function of Adopt is to put the parameter iterator at the head of iterator's one-way list. void _Adopt(const _Container_base12 *_Parent) { // adopt this iterator by parent if (_Parent != 0) { // have a parent, do adoption _Container_proxy *_Parent_proxy = _Parent->_Myproxy; #if _ITERATOR_DEBUG_LEVEL == 2 if (_Myproxy != _Parent_proxy) { // change parentage _Lockit _Lock(_LOCK_DEBUG); //Cut yourself off from previous container s _Orphan_me(); //The following three lines of code put themselves in the header of the list _Mynextiter = _Parent_proxy->_Myfirstiter; _Parent_proxy->_Myfirstiter = this; //Proxy synchronization between self-proxy and container _Myproxy = _Parent_proxy; } #else /* _ITERATOR_DEBUG_LEVEL == 2 */ //Proxy synchronization between self-proxy and container _Myproxy = _Parent_proxy; #endif /* _ITERATOR_DEBUG_LEVEL == 2 */ } } void _Clrcont() { // disown owning container _Myproxy = 0; } const _Container_base12 *_Getcont() const { // get owning container return (_Myproxy == 0 ? 0 : _Myproxy->_Mycont); } _Iterator_base12 **_Getpnext() { // get address of remaining iterator chain return (&_Mynextiter); } //Isolate yourself: remove self from list void _Orphan_me() { // cut ties with parent #if _ITERATOR_DEBUG_LEVEL == 2 if (_Myproxy != 0) { // adopted, remove self from list _Iterator_base12 **_Pnext = &_Myproxy->_Myfirstiter; //where Cycle Finds Yourself while (*_Pnext != 0 && *_Pnext != this) _Pnext = &(*_Pnext)->_Mynextiter; //The meaning of this code is to remove yourself from the iterator one-way list. Think about it. *_Pnext = _Mynextiter; //When the agent is set to zero, it can't use the container stored in the agent, and it's disconnected from the container (vector). _Myproxy = 0; } #endif /* _ITERATOR_DEBUG_LEVEL == 2 */ } _Container_proxy *_Myproxy; _Iterator_base12 *_Mynextiter;//The iterator of the same vector is put in a one-way list, which is used to point to the next iterator };
template<class _Category, class _Ty, class _Diff, class _Pointer, class _Reference, class _Base> struct _Iterator012//It's coquettish to put this pre-statement here. : public _Base//For vector iterators, this _Base is _Iterator_base12 typedef _Category iterator_category; typedef _Ty value_type; typedef _Diff difference_type; typedef _Diff distance_type; // retained typedef _Pointer pointer; typedef _Reference reference; };
The most important thing in iterators is pointers to member variables that store object types. Then a series of operations are performed according to this pointer to make iterator and pointer behave similarly.
//xutility file typedef _Iterator_base12 _Iterator_base;
// TEMPLATE CLASS _Vector_const_iterator template<class _Myvec> class _Vector_const_iterator : public _Iterator012<random_access_iterator_tag,//Random access tag typename _Myvec::value_type, typename _Myvec::difference_type, typename _Myvec::const_pointer, typename _Myvec::const_reference, //_ Iterator_base is _Iterator_base 12, and _Iterator_base will become the base class of _Vector_const_iterator. _Iterator_base> { // iterator for nonmutable vector public: typedef _Vector_const_iterator<_Myvec> _Myiter; typedef random_access_iterator_tag iterator_category; typedef typename _Myvec::pointer _Tptr; typedef typename _Myvec::value_type value_type; typedef typename _Myvec::difference_type difference_type; typedef typename _Myvec::const_pointer pointer; typedef typename _Myvec::const_reference reference; typedef pointer _Unchecked_type; //Assigning a pointer class pointer to our _Ptr _Myiter& _Rechecked(_Unchecked_type _Right) { // reset from unchecked iterator this->_Ptr = (_Tptr)_Right; return (*this); } //Return true_Ptr _Unchecked_type _Unchecked() const { // make an unchecked iterator return (_Unchecked_type(this->_Ptr)); } reference operator*() const { // return designated object //Returns what the real _Ptr refers to, so there is (* iter). member variable / member function operation return (*this->_Ptr); } pointer operator->() const { // return pointer to class object //* this returns the iterator, adds * returns what _Ptr points to, and then takes the address. //So there is ITER - > member variable / member function operation return (&**this); } //Here take the overloaded + + operation for example. Actually, the operation is all on the _Ptr pointer. After ++, the iterator has not changed, but the pointer has changed. _Myiter& operator++() { // preincrement #if _ITERATOR_DEBUG_LEVEL == 2 if (this->_Getcont() == 0 || this->_Ptr == 0 || ((_Myvec *)this->_Getcont())->_Mylast <= this->_Ptr) { // report error _DEBUG_ERROR("vector iterator not incrementable"); _SCL_SECURE_OUT_OF_RANGE; } #elif _ITERATOR_DEBUG_LEVEL == 1 _SCL_SECURE_VALIDATE(this->_Getcont() != 0); _SCL_SECURE_VALIDATE_RANGE( this->_Ptr != 0 && this->_Ptr < ((_Myvec *)this->_Getcont())->_Mylast); #endif /* _ITERATOR_DEBUG_LEVEL */ ++this->_Ptr; return (*this); } _Tptr _Ptr; // pointer to element in vector };
// TEMPLATE CLASS _Vector_iterator template<class _Myvec> class _Vector_iterator : public _Vector_const_iterator<_Myvec> { // iterator for mutable vector public: typedef _Vector_iterator<_Myvec> _Myiter; typedef _Vector_const_iterator<_Myvec> _Mybase; typedef random_access_iterator_tag iterator_category; typedef typename _Myvec::value_type value_type; typedef typename _Myvec::difference_type difference_type; typedef typename _Myvec::pointer pointer; typedef typename _Myvec::reference reference; }
vector and iterator Association
The most common code we use in iterator is the following.
vector<int>::iterator iter = vecInt.begin();
begin() returns an iterator
//Member functions of vector s iterator begin() { // return iterator for beginning of mutable sequence return (iterator(this->_Myfirst, this)); }
_ Mybase is _Vector_const_iterator
//iterator constructor _Vector_iterator(pointer _Parg, const _Container_base *_Pvector) : _Mybase(_Parg, _Pvector) { // construct with pointer _Parg }
The following constructor function simply points _Ptr to the element of our vector, associates the iterator with the vector (proxy), and adds the iterator to the iterator list associated with the vector.
//_ Vector_const_iterator constructor _Vector_const_iterator(_Tptr _Parg, const _Container_base *_Pvector) : _Ptr(_Parg) { // construct with pointer _Parg this->_Adopt(_Pvector); }
A few more words about this agent. Container has a proxy pointer, iterator also has a proxy pointer, all pointing to proxy. This proxy is created in the _Vector_val constructor. Proxy has two pointers, one pointing to container and one pointing to the first iterator in the iterator list.
Some other important functions
void _Orphan_range(pointer _First, pointer _Last) const { // orphan iterators within specified (inclusive) range _Lockit _Lock(_LOCK_DEBUG); const_iterator **_Pnext = (const_iterator **)this->_Getpfirst(); if (_Pnext != 0) { while (*_Pnext != 0) { //Not in the range of _First and _Last, just do the operation of returning to the next iterator if ((*_Pnext)->_Ptr < _First || _Last < (*_Pnext)->_Ptr) _Pnext = (const_iterator **)(*_Pnext)->_Getpnext(); //Within the scope else { // orphan the iterator (*_Pnext)->_Clrcont();//proxy set 0 *_Pnext = *(const_iterator **)(*_Pnext)->_Getpnext(); } } } }
iterator erase(const_iterator _Where) { // erase element at where if (_VICONT(_Where) != this || _VIPTR(_Where) < this->_Myfirst || this->_Mylast <= _VIPTR(_Where)) _DEBUG_ERROR("vector erase iterator outside range"); //Move the element behind where one bit forward _Move(_VIPTR(_Where) + 1, this->_Mylast, _VIPTR(_Where)); //After moving, destruct the last element _Destroy(this->_Mylast - 1, this->_Mylast); //_ Where sets the proxy of the iterator to zero, so if there are iterators pointing to the following elements, those iterators will not work. _Orphan_range(_VIPTR(_Where), this->_Mylast); //One element is missing, _Mylast minus one --this->_Mylast; //It still returns _Where, but the content pointed to by _Where has changed. It's the element behind _Where before it's deleted. return (_Make_iter(_Where)); }
If you have the following lines of code
std::vector<int> vecInt; vecInt.push_back(1); vecInt.push_back(2); vecInt.push_back(3); std::vector<int>::iterator iter1 = vecInt.begin()+1; std::vector<int>::iterator iter2 = vecInt.begin()+1; vecInt.erase(iter1); *iter2;
After running, there will be a "Debug Assertion Failed" bullet-frame error. The reason is that after deleting iter1, the proxy of iter2 has been set to 0. In the operator * function of _Vector_const_iterator, there are the following code fragments. This - > Getcont () uses the proxy of the iterator. If the empty container returns empty, the programmer often sees the phrase "vector iterator is not dereferencable", but does not know it.
reference operator*() const { // return designated object #if _ITERATOR_DEBUG_LEVEL == 2 if (this->_Getcont() == 0 || this->_Ptr == 0 || this->_Ptr < ((_Myvec *)this->_Getcont())->_Myfirst || ((_Myvec *)this->_Getcont())->_Mylast <= this->_Ptr) { // report error _DEBUG_ERROR("vector iterator not dereferencable"); _SCL_SECURE_OUT_OF_RANGE; } #endif /* _ITERATOR_DEBUG_LEVEL */ }
11