C + + Hou Jie STL standard library and Generic Programming Notes

C++STL

This blog post refers to Mr. Hou Jie's STL generic programming course and records the more important parts for future review. I highly recommend Mr. Hou Jie's C + + series. It really exists like the Bible!

1.STL six components

  • Containers

  • Allocators

  • Algorithms

  • Iterators

  • Adapters

  • Functors

2. Distributor

allocator completes allocate() and deallocate() with:: operator new and:: operator delete.

In VC6.0, the allocator is defined as follows. It can be seen that it only encapsulates operator new and operator delete:

template<class _Ty>
class allocator
{
public:
	typedef _SIZT size_type;  // size_t
    typedef _PDFT difference_type; // ptrdff_t
    typedef _Ty _FARQ pointer; 
    typedef _Ty value_type;
    
    pointer allocate(size_type _N,const void*)
    {
    	return (_Allocate((difference_type)_N,(pointer)0));    
    }
    void deallocate(void _FARQ *_P, size_type) {
        operator delete(_P);
    };
private:
	inline _Ty _FARQ*_Allocate(_PDFT _N, _Ty _FARQ *)
    {
        if(_N<0)_N=0;
        return ((_Ty _FARQ *) operator new((_SIZT) _N * sizeof(_Ty)));
    }
};
//If we use it ourselves, we need to call it like this
//Assign 512int
//It can be found that during allocation, we need to know how much space we need and how much space we release accordingly. This is very inconvenient
int *p = allocator<int>().allocate(512,(int*)0);
allocator<int>().deallocate(p,512);

However, in the version of gcc2.9, allocator is not the default allocator of gcc2.9, but STD:: allocator

class alloc
{
protected:
    enum { _S_align = 8 };
    enum { _S_max_bytes = 128 };
    enum { _S_free_list_size = (size_t) _S_max_bytes / (size_t) _S_align };
    union _Obj {
        union _Obj *_M_free_list_link;
        char _M_client_data[1];    // The client sees this.
    };
    ...
}

After gcc4.9, the default allocator is changed to allocator, which returns to the simple encapsulation of:: operator new and:: operator delete. Alloc is renamed__ gnu_cxx::__pool_alloc.

3. Container

The container conforms to the front closed and back open interval [] () [), that is, c.begin() points to the head, and c.end() points to the next of the tail element.

Container<T>c
    ...
Container<T>::iterator ite = c.begin();
for(;ite!=c.end();++ite)
    ...

In C++11, the above program can be rewritten with range base for statement

for(auto elem:c){
    ...
}

Vessel structure and classification

- Sequence Container
  - Array   C++11
  - Vector
  - Deque
  - List
  - Forward-List  C++11
- Associative Container
  - Set/Multiset
  - Map/Multimap
  - Unordered Set/MultiSet   C++11
  - Unordered Map/MultiMap  C++11

Sequencecontainer: array size is fixed and cannot be expanded. Vector can be expanded automatically. Deque two-way expansion, list (two-way linked list) and forward list (one-way linked list).

AssociativeContainer:Set(Key==Value), Map(Key,Value). Because the storage structure is random rather than continuous, the iterator does not meet the random access iterator (random access iterator), which is implemented by the red black tree. The Unordered data structure is implemented by HashTable.

3.1 in depth exploration

List is a two-way linked list. The ordinary pointer can no longer meet the requirements of list iterator. Because the space of list is discontinuous, the iterator of list needs to have the functions of forward and backward. Therefore, the type of list iterator is bidirectionally iterator.

template<class T,class Alloc=alloc>
class list
{
protected:
	typedef __list_node<T> list_node;
public:
	typedef list_node* link_type;
    typedef __list_iterator<T,T&,T*>iterator;
protected:
    link_type node;
...
}
template<class T>
struct __list_node
{
	typedef void* void_pointer;
    void_pointer prev;
    void_pointer next;
    T data;
}

List (two-way linked list). In the GNU2.9 source code, the node attribute is maintained in the list class. This attribute is a structure containing two pointers prev, next and current data. Based on the principle that containers should be closed before they are opened, a gray zone will be added to the head of the list, begin() will get the next node of the gray zone, and end() will get the gray zone.

// __list_iterator source code
template<class T,class Ref,class Ptr>
struct __list_iterator
{
	typedef __list_iterator<T,Ref,Ptr> self;
	typedef bidirectional_iterator_tag iterator_category;
	typedef T value_type;
	typedef Ptr pointer;
	typedef Ref reference;
	typedef __list_node<T>* link_type;
	typedef ptrdiff_t difference_type;

	link_type node;

	reference operator*()const{return *(node).data;}
	pointer operator->()const{return &(operator*());}
	self& operator++(){
		node = (link_type)(*node).next;
		return *this;
	}
	self operator++(int){
		self temp = *this;
		++*this;
		return temp;
	}
	...
};

__list_iterator defines five parameters for Iterator traits:

  • bidirectional_iterator_tag

  • T(value_type)

  • ptr(pointer)

  • Ref(reference)

  • ptrdiff_t(difference_type)

At the same time, the iterator of the list is a class, mainly because the internal data structure of the list is stored randomly, so a smart iterator is needed to simulate the function that the list can support random access. In order to do this, the iterator overloads operators such as operator *, operator - >, operator + +, operator + + (int).

In the above source code, in the overloaded operator++(int) function, it should be noted that self temp = *this will not call operator * (), because the copy construct has been called before.

In STL algorithm, we need to pass in the iterator of the container, and then judge the type of iterator according to inference.

3.2 Iterator traits

The iterator is the bridge between the container and algorithms. If the container wants to use the algorithms in algorithms, the iterator is essential, but the iterator characteristics of each container are inconsistent. How can the algorithm effectively know which iterator to call and what to do?

template<typename _ForwardIterator>
inline void rotate(_ForwardIterator __first,_ForwardIterator __middle,_ForwardIterator __last){
	...
	std::__rotate(__first,__middle,__last,std::__iterator_category(__first));
}


template<typename _Iter>
inline typename iterator_traits<_Iter>::iterator_category __iterator_category(const _Iter&){
	return typename iterator_traits<_Iter>::iterator_category();
}

As shown above, it is part of the code of the algorithm rotate. It can be seen that the internal operation of the algorithm also needs to obtain the Iterator type.

At the same time, in order to obtain the type of iterator, the algorithm also declares five association types of iterator. In order to answer the questions of algorithms, the iterator (provided that the iterator is a class) must provide:

1.Iterator_category
2.value_type
3.pointer
4.reference
5.diffrence_type

At the same time, in order to consider the problem of compatibility, an intermediate layer "traits" is added between the iterator and the algorithm. At the same time, it is also used to distinguish whether the iterator is a class type or a non class (pointer) type

So here's the question. How does traits distinguish between class and non class iterator s? The answer is partial specialization.

// iterator is of class type, and the default generic type is taken directly
template<class I>
struct iterator_traits {
    typedef typename I::iterator_category 	iterator_category;
    typedef typename I::value_type 			value_type;
    typedef typename I::difference_type 	difference_type;
    typedef typename I::pointer 			pointer;
    typedef typename I::reference 			reference;
};

// iterator is a pointer
template<class T>
struct iterator_traits<T *> {
    typedef random_access_iterator_tag 		iterator_category;
    typedef T 								value_type;
    typedef ptrdiff_t 						difference_type;
    typedef T*								pointer;
    typedef T&								reference;
};

// iterator is a constant pointer
template<class T>
struct iterator_traits<const T *> {
    typedef random_access_iterator_tag 		iterator_category;
    typedef T 								value_type;	
    typedef ptrdiff_t 						difference_type;
    typedef const T*						pointer;
    typedef const T&						reference;
};

The main purpose of value_type is to declare variables, and it is useless to name a variable that cannot be assigned, so const should not be added to value_type of iterator (even const iterator).

3.3 Vector depth exploration

// vector source code
template<class T,class Alloc=alloc>
class vector
{
public:
	typedef T value_type;
	typedef value_type* iterator;
	typedef value_type& reference;
	typedef size_t size_type;
protected:
	iterator start;
	iterator finish;
	iterator end_of_storage;
public:
	iterator begin(){return start;}
	iterator end(){return finish;}
	size_type size()const{return (size_type)end_of_storage-begin();}
	bool empty()const{return begin()==end();}
	reference operator[](size_type n){
		return *(begin()+n);
	}
	reference front(){return *begin();}
	reference back(){return *(end()-1);}
    ...
};

The iterator start of the vector container points to the first element, and finish points to the next element of the last element, which meets the characteristics of closing before opening. end_of_storage is the capacity of the vector. The vector is continuous in use and implementation, so the iterator adopts non class type.

​ vector.push_back() First, we will judge whether the end_of_storage is full. If it is full, it will be expanded twice, and then add elements. Note: the expansion process is not to add capacity after the original space, but to re apply for a continuous memory space, copy the original data to the new space, and then release the memory in the original space. Therefore, after expansion, the original iterator will It will fail!

void push_back(const T& x){
	if(finish!=end_of_storage){
		construct(finish,x);
		++finish;
	}else
		insert_aux(end(),x);
}

insert_aux is a vector container. It is used to insert elements at any position inside. If the memory is insufficient, it will be expanded.

template<class T, class Alloc>
void vector<T, Alloc>::insert_ux(iterator position, const T &x) {
    if (finish != end_of_storage) {     // If there is spare space, move the insertion point back one bit and insert the element
        construct(finish, *(finish - 1));   // Take the last element value of vector as the initial value of the new node
        ++finish;
        T x_copy = x;
        copy_backward(position, finish - 2, finish - 1);
        *position = x_copy;
    } else {
        // If there is no spare space, expand the capacity first and then insert it
        const size_type old_size = size();
        const size_type len = old_size != 0 ?: 2 * old_size:1;  // The length after expansion is twice the original length

        iterator new_start = data_allocator::allocate(len);
        iterator new_finish = new_start;
        try {
            new_finish = uninitialized_copy(start, position, new_start);    // Copy elements before insertion point
            construct(new_finish, x);                                       // Insert a new element and adjust the water level
            ++new_finish;
            new_finish = uninitialized_copy(position, finish, new_finish);  // Copy elements after insertion point
        }
        catch (...) {
            // If the insertion fails, it rolls back, frees up memory and throws an error
            destroy(new_start, new_finish) :
            data_allocator::deallocate(new_start, len);
            throw;
        }
        // Free the memory occupied by the original container
        destroy(begin(), end());
        deallocate();
        // Adjust iterator
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
    }
};

3.4 in depth exploration of array and ForwardList

  • array container

In C++11, a new container std::array is added, which is similar to the array operation we usually use. Unlike the dynamic scalability of vector, it is statically non scalable.

template<typename _Tp,std::size_t _Nm>
struct array
{
	typedef _Tp value_type;
	typedef _Tp* pointer;
	typedef value_type* iterator;
	// Support for zero-sized arrays mandatory
	value_type _M_instance[_Nm?_Nm:1];

	iterator begin(){return iterator(_M_instance[0]);}
	iterator end(){return iterator(_M_instance[_Nm]);}
	...
};
// test
array<int,10>arrayins;
auto iter = arrayins.begin();
// array<int,10>::iterator ite = iter+3;
cout<<*iter<<endl;
  • forward_list container

The forward_list is also a container provided by C++11. Unlike the list container, it is a single linked list. Compared with the list that needs to store the next and pre nodes for each Node node, the forward_list only needs to store the next Node, and the forward_list will be more lightweight.

3.5 Deque deep exploration

  • deque container (double ended queue)

deque is a continuous linear space with two-way openings. deque supports the insertion and deletion of elements from both ends. deque has no concept of capacity because it is dynamically combined in piecewise continuous space. A new space can be added and connected at any time.

template<class T,class Alloc=alloc,size_t BufSiz=0>
class deque
{
public:
	typedef T value_type;
	typedef __deque_iterator<T,T&,T*,BufSiz> iterator;

protected:
	typedef pointer* map_pointer; //T**
	iterator start;
	iterator finish;
	map_pointer map;
	size_type map_size;

public:
	iterator begin(){return start;}
	iterator end(){return end;}
	size_type size(){return finish-start;}
	...
};

In order to maintain the illusion of overall continuity, the type of iterator with value will be more complex. That is, map (type T * *) is used as the master control. In fact, a map is a space of continuous size, in which each element is called a node. Each node points to a continuous linear space at the other end (the buffer in the figure above). The buffer is the real storage space of deque.

Determination of buffer size:

/*
n If it is not 0, n is returned, and the buffer size is determined by the user
n If it is 0, the buffer size uses the default value. If sz(sizeof(value_type)) is less than 512, 512/sz is returned; otherwise, 1 is returned
*/
inline size_t __deque_buf_size(size_t n,size_t sz)
{
	return n!=0?n:(sz<512?size_t(512/sz):size_t(1));
}

About the iterator design of deque:

template<class T,class Ref,class Ptr,size_t BufSiz>
struct __deque_iterator
{
	typedef random_access_iterator_tag iterator_category; //1
	typedef T value_type;	//2
	typedef Ptr pointer;	//3
	typedef Ref reference;	//4
	typedef size_t size_type;	
	typedef ptrdiff_t difference_type; //5
	typedef T** map_pointer;
	typedef __deque_iterator self;

	T* cur;
	T* first;
	T* last;
	map_pointer node;
	...
};

The iterator of deque has four parts. cur points to the current element of the buffer, first points to the head of the buffer, last points to the tail, and node points to the map (master control center).

Deque < T >:: the insert() insertion function first determines whether the position of the incoming iterator is in the first half or the second half of the container, and then inserts the shorter section.

iterator inset(iterator position,const value_type& x)
{
	if(position.cur==start.cur){
		push_front(x);
		return start;
	}else if(position.cur==finish.cur){
		push_back(x);
		return finish
	}else{
		return insert_aux(position,x);
	}
}

If the insertion position is the head of the container, push directly_ Front, if the position is the tail of the container, push directly_ back. In other cases, insert is called_ Aux method:

template<class T,class Alloc,size_t BufSize>
typename deque<T,Alloc,BufSize>::iterator 
deque<T,Alloc,BufSize>::insert_aux(iterator pos,const value_type& x){
	difference_type index = pos-start; //Number of elements before insertion point
	value_type x_copy = x;
	if(index<size()/2){			//If the number of elements before the placement point is small
		push_front(front());	//Add an element with the same value as the first element at the front end
		...
		copy(front2,pos1,front1); //Element movement
	}else{					//Less after insertion point
		push_back(back());	//Add an element with the same value as the last element at the end
		...
		copy_backward(pos,back2,back2); //Element movement
	}
	*pos = x_copy;
	return pos;
}

How deque simulates continuous spaces:

reference operator[](size_type n){
	return start[difference_type(n)];
}

reference front(){
	return *start;
}
reference back(){
    iterator tmp = finish;
    --temp;
    return *temp;
}

size_type size()const{
	return finish-start;
}

bool empty()const{
	return finish==start;
}

Important operator overloading:

reference operator*()const{
	return *cur;
}

pointer operator->()const{
	return &(operator*());
}

//The distance between two iterators is equivalent to the total length of buffers between two iterators + it1 to its buffer tail length + it2 to its buffer head length
difference_type operator-(const self& x)const{
	return difference_type(buffer_size())*(node-x.node-1)+(cur-first)+(x.last-x.cur);
}
self& operator++(){
    ++cur;
    if(cur==last){
    	set_node(node+1); //Jump to the starting node of the next node
    	cur = first;
    }
    return *this;
}

self operator++(int){
    self tmp = *this;
    ++*this;
    return temp;
}

self& operator--(){
    --cur;
    if(cur==first){
    	set_node(node-1); //Jump to the last node of the previous node
    	cur = last;
    }
    return *this;
}

self operator--(int){
    self tmp = *this;
    --*this;
    return temp;
}


void set_node(map_pointer new_node){
    node = new_node;
    first = *node;
    last = first+difference_type(buffer_size());
}

self& operator+=(difference_type n)
{
    difference_type offset = n+(cur-first);
    if(offset>=0&&offset<difference_type(buffer_size()))
    // The target is in the same buffer
    cur +=n;
    else{
        // The target is not in the same buffer
        difference_type node_offset = offset>0?offset/difference_type(buffer_size()):-difference_type((-offset-1)/buffer_size())-1;
        // Switch to the correct buffer
        set_node(node+node_offset);
        // Switch to the correct element
        cur = first+(offset-node_offset*difference_type(buffer_size()));
    }
    return *this;
}

self operator+(difference_type n){
    self tmp = *this;
    return temp+=n;
}

3.6 deep exploration of queue and Stack

Containers queue and stack are similar in the implementation of STL. Deque is used as the adapter, and a deque is encapsulated by default as the underlying container. Most of the APIs of the upper container are implemented by operating deque's API. Queue is FIFO and stack is FIFO. There are strict requirements for entry and exit, so the two containers are not allowed to traverse, so they have no iterators.

  • queue

template<class T, class Sequence=deque<T>>
class queue {
public:
    typedef typename Sequence::value_type value_type;
    typedef typename Sequence::size_type size_type;
    typedef typename Sequence::reference reference;
    typedef typename Sequence::const_reference const_reference;
protected:
    Sequence c;     // The bottom container is deque < T > by default
public:
    bool empty() const { return c.empty(); }
    size_type size() const { return c.size(); }
    reference front() { return c.front(); }
    const_reference front() const { return c.front(); }
    reference back() { return c.back(); }
    const_reference back() const { return c.back(); }
    void push(const value_type &x) { c.push_back(x); }
    void pop() { c.pop_front(); }
    // ...
};
  • stack

template<class T, class Sequence=deque<T> >
class stack {
public:
    typedef typename Sequence::value_type value_type;
    typedef typename Sequence::size_type size_type;
    typedef typename Sequence::reference reference;
    typedef typename Sequence::const_reference const_reference;
protected:
    Sequence c;// The bottom container is deque < T > by default
public:
    bool empty() const { return c.empty(); }
    size_type size() const { return c.size(); }
    reference top() { return c.back(); }
    const_reference top() const { return c.back(); }
    void push(const value_type &x) { c.push_back(x); }
    void pop() { c.pop_back(); }
    // ...
};

You can also specify other containers, such as list and vector, as the underlying containers of stack and queue, because they also implement the required methods.

queue<int, list<int>> q1;
for (long i = 0; i < 10; ++i) {
    q1.push(rand());
}

stack<int, list<int>> s1;
for (long i = 0; i < 10; ++i) {
    s1.push(rand());
}

stack<int, vector<int>> s2;
for (long i = 0; i < 10; ++i) {
    s2.push(rand());
}

However, if the wrong container is specified (the container does not implement the corresponding method), the compilation will not report an error, indicating that the compiler will not make a comprehensive check when processing the template, so it is best to use the default template as their underlying container.

3.7 Rb_Tree deep exploration

Red black tree is a balanced binary Search tree. Features: the arrangement rules are conducive to Search and Insert, and maintain a moderate balance - no nodes are too deep. rb_tree provides two insertion operations: inset_unique and insert_equal. The former requires that the key is unique in the tree (multimap/set is not applicable), and the latter key can exist repeatedly.

template<class Key,class Value,class KeyOfValue,class Compare,class Alloc=alloc>
class rb_tree
{
protected:
	typedef __rb_tree_node<Value> rb_tree_node;
public:
	typedef rb_tree_node* link_type;
protected:
	size_type node_count;	//rb_ Number of nodes in the tree
    link_type header;		//Head node
    Compare Key_compare;	//Key sorting method
};

​ rb_ In the use of tree, you need to provide four parameters: key, Value, keyofvalue (method of extracting key) and compare (method of comparing the size of key).

template <class T>
struct identity:public unary_function<T,T>{
    const T& operator()(const T& ref)const
    {
        return ref;
	}
}

template <class T>
struct less:public binary_function<T,T,bool>{
    bool operator()(const T&x,const T&y){
        return x<y;
    }
}

void RbTreeTest(){
    _Rb_tree<int,int,identity<int>,less<int>> tree;
    cout << itree.empty() << endl;  //1
	cout << itree.size() << endl;   //0

	itree._M_insert_unique(3);
	itree._M_insert_unique(8);
	itree._M_insert_unique(5);
	itree._M_insert_unique(9);
	itree._M_insert_unique(13);
	itree._M_insert_unique(5);  //no effect, since using insert_unique().
	cout << itree.empty() << endl;  //0
	cout << itree.size() << endl;   //5
	cout << itree.count(5) << endl; //1

	itree._M_insert_equal(5);
	itree._M_insert_equal(5);
	cout << itree.size() << endl;   //7, since using insert_equal().
	cout << itree.count(5) << endl; //3    
}

3.8 deep exploration of set and Multiset

set/multiset to rb_tree is the underlying structure, so it has the function of automatic sorting of elements. The sorting is based on the key, and the Value and key of set/multiset elements are one (Value is the Key).

Note: we cannot use the set/multiset iterator to change the element value (because the key has its strict arrangement rules). The iterator of set/multiset is the const iterator of the rbtree at its bottom.

The insert() of set uses RB_ Inset of tree_ unique().

The insert() of multiset uses RB_ Inset of tree_ equal().

template<class Key,
         class Compare = less<Key>,
         class Alloc = alloc>
class set {
public:
    typedef Key key_type;
    typedef Key value_type;
    typedef Compare key_compare;
    typedef Compare value_compare;
private:
    typedef rb_tree <key_type, 
    				 value_type, 
    				 identity<value_type>, 
    				 key_compare, 
    				 Alloc> rep_type;
    rep_type t;		// Internal rb_tree container
public:
    typedef typename rep_type::const_iterator iterator;
};

From the source code of set, we can see that most of the core operations of set are actually thrown to rbtree. Here, set can be regarded as a container adapter.

3.9 deep exploration of map and Multimap

Map/multimap to rb_tree is the underlying structure, so it has the function of automatic element sorting. The sorting is based on key.

Note: we can't use the iterator of map/multimap to change the element value (because the key has its strict arrangement rules), but we can use it to change the data of the element. map/multimap automatically sets the keytype specified by the user to const to prohibit the user from assigning a key to the element.

The insert() of map uses RB_ Inset of tree_ unique().

The insert() of multimap uses RB_ Inset of tree_ equal().

template<class key,Class T,class Compare=less<key>,class Alloc=alloc>
class map{
public:
   typedef key key_type;
   typedef T data_type;
   typedef T mapped_type;
   typedef pair<const key,T> value_type // Specify const key here to prevent the user from modifying the key
   typedef Compare key_compare;
private:
   typedef rb_tree<key_type,value_type,select1st<value_type>,key_compare,Alloc> rep_type
   rep_type t;	//rb_tree
public:
    typedef typename rep_type::iterator iterator;
}

template<class Pair>
struct select1st:public unary_function<Pair,typename Pair::first_type>
{
    const typename Pair::first_type& operator()(const Pair& x){
        return x.first;
    }
}

Special symbol overload operator [] in map

operator[](const key_type& _k)

If k exists, return the iterator. If it does not exist, create the k in the appropriate position

mapped_type& operator[](const key_type& _k){
    iterator _i = lower_bound(_k);
 	if(_i=end()||key_comp()(_k,(*_i).first))
        _i = insert(_i,value_type(_k,mapped_type()));
    return(*_i).second;
}

operator [] cannot be used in multimap.

3.10 HashTable depth exploration

rehashing is performed when the number of elements is greater than buckets. The growth multiple (under GNUC) is twice. After the growth, all elements are rearranged to realize the hash distribution of elements.

hashtable source code:

//HashFcn is a method to determine the element number in Hashtable, usually a function object
//Extractkey if the element is a pair of pairs, you need to tell the method of extracting the key
//Equalkey method for comparing key sizes
template<class value,
		class key,
		class HashFcn,
		class Extrackey,
		class EqualKey,
		class Alloc=alloc>
class hashtable{
public:
    typedef HashFnc hasher;
    typedef EqualKey key_equal;
    typedef size_t size_type;

private:
    hasher hash;
    key_equal equals;
    Extrackey get_key;

    typedef _hashtable_node<value> node;
    
    vector<node*,Alloc> buckets;
    size_type num_elements;
    
public:
    size_type bucket_count()const{return buckets.size();}
 ...
}

template<class value>
struct _hashtable_node{
	_hashtable_node* next;
    value val;
...
}

template<class value,class key,class HashFcn,class Extrackey,class EqualKey,class Alloc=alloc>
struct _hashtable_iterator{
	...
    node* cur;
    hashtable* ht;
}
void hashtableTest(){
	hashtable<const char*,
              const char*,
    		  hash<const char*>,
    		  identity<const char*>,
    		  eqstr> 
    ht(50,hash<const char*>(),eqstr());
    ht.insert_unique("zsas");
	ht.insert_unique("asdfs");
}

struct eqstr{
    bool operator()(const char*s1,const char*s2)const{
        return strcmp(s1,s2)==0;
    }
}

How to use hashfunction?

hashfun source code:

template<class key>struct hash{};

_STL_TEMPLATE_NULL struct hash<char>{size_t operator()(const char x)const (return x;)};
_STL_TEMPLATE_NULL struct hash<short>{size_t operator()(const short x)const (return x;)};
_STL_TEMPLATE_NULL struct hash<unsigned short>{size_t operator()(const unsigned short x)const (return x;)};
_STL_TEMPLATE_NULL struct hash<int>{size_t operator()(const int x)const (return x;)};
_STL_TEMPLATE_NULL struct hash<unsigned int>{size_t operator()(const unsigned int x)const (return x;)};
_STL_TEMPLATE_NULL struct hash<long>{size_t operator()(const long x)const (return x;)};
_STL_TEMPLATE_NULL struct hash<unsigned long>{size_t operator()(const unsigned long x)const (return x;)};

_STL_TEMPLATE_NULL struct hash<char*>{size_t operator()(const char* s)const (return _stl_hash_string(s);)};
_STL_TEMPLATE_NULL struct hash<const char*>{size_t operator()(const char* s)const (return _stl_hash_string(s);)};

inline size_t _stl_hash_string(const char* s){
    unsigned long h=0;
    for(;*s;++s){
        h=5*h+*s;
    }
    return size_t(h);
}

//Note that the special version of hashfunc template of string type is not provided in stl. You need to provide it yourself

3.11 use of unordered containers

The container introduced by C++11 is unordered_set,unordered_multiset,unordered_map and unordered_multimap is renamed from the lower container hash of GCC2.9_ set,hash_multiset,hash_map and hash_multimap, whose bottom layer encapsulates hashtable. The usage is similar to set, multiset, map and multimap.

void UnorderedSetCompare() {
    unordered_multiset<string>s;
    char buf[10];
    for (long i = 0; i < INPUTSIZE; i++) {
        try
        {
            snprintf(buf, 10, "%d", rand());
            s.insert(string(buf));
        }
        catch (const std::exception& p)
        {
            cout << "i= " << i << " " << p.what() << endl;
            abort();
        }
    }
    cout << "milli-seconds: " << clock() << endl;
    cout << "s.size(): " << s.size() << endl;
    cout << "s.Max_Size(): " << s.max_size() << endl;
    cout << "s.bucket_count():" << s.bucket_count() << endl;
    cout << "unordered_multiset.load_factor()= " << s.load_factor() << endl;
    cout << "unordered_multiset.max_load_factor()= " << s.max_load_factor() << endl;
    cout << "unordered_multiset.max_bucket_count()= " << s.max_bucket_count() << endl;

    for (unsigned i = 0; i < 20; i++) {
        cout << "bucket #" << i << " has " << s.bucket_size(i) << " elemt " << endl;
    }
    string target = get_a_string_target();
    Clock_Time start_time = clock();

    auto ite = ::find(s.begin(), s.end(), target);

    cout << "::find(),milli-seconds: " << (clock() - start_time) << endl;
    if (ite != s.end()) {
        cout << "Find Value!" << endl;
    }
    else {
        cout << "Not Find Value" << endl;
    }

    start_time = clock();

    ite = s.find(target);	//Much faster than the global:: sort function

    cout << "s.find(),milli-seconds: " << (clock() - start_time) << endl;
    if (ite != s.end()) {
        cout << "Find Value!" << endl;
    }
    else {
        cout << "Not Find Value" << endl;
    }
}

4. Algorithm

4.1 General structure of algorithm

The algorithm cannot see the container, so all the information it needs must be obtained from the iterator, and the iterator (provided by the container) must answer all the questions of the algorithm before the container can match all the operations of the algorithm.

  • All algorithms in the STL source code follow the following format:
template<typename iterator>
Algorithm(iterator itr1,iterator itr2){
    ...
}

template<typename iterator,typename Cmp>
Algorithm(iterator itr1,iterator itr2,Cmp comp){
    ...
}

4.2 iterator classification


Iterator's associated type iterator_category indicates the iterator type, which is classified into the following five types:

1.	struct input_iterator_tag{};
2.	struct ouput_iterator_tag{};
3.	struct forward_iterator_tag:public input_iterator_tag{};
4.	struct bidirectional_iterator_tag:public forward_iterator_tag{};
5.	struct random_access_iterator_tag:public bidrectional_iterator_tag{};

The iterator type is represented by class instead of enum for the following two considerations:

  • Using class inheritance, you can represent dependencies of different iterator types.
  • STL algorithm can call different versions of overloaded functions according to the type of iterator passed in.
void _displayIteratorCategory(random_access_iterator_tag) {
    cout << "random_access_iterator_tag" << endl;
}
void _displayIteratorCategory(forward_iterator_tag) {
    cout << "forward_iterator_tag" << endl;
}
void _displayIteratorCategory(bidirectional_iterator_tag) {
    cout << "bidirectional_iterator_tag" << endl;
}
void _displayIteratorCategory(input_iterator_tag) {
    cout << "input_iterator_tag" << endl;
}
void _displayIteratorCategory(output_iterator_tag) {
    cout << "output_iterator_tag" << endl;
}

template<typename T>
void displayIteratorCategory(T iter) {
    typename iterator_traits<T>::iterator_category cag;
    _displayIteratorCategory(cag);
}

void iteratorCategoryTest() {

    displayIteratorCategory(vector<int>::iterator()); //random_access_iterator
    displayIteratorCategory(array<int,10>::iterator());	//random_access_iterator
    displayIteratorCategory(list<int>::iterator());	//bidirectional_iterator
    displayIteratorCategory(forward_list<int>::iterator());	//forward_iterator
    displayIteratorCategory(deque<int>::iterator());	//random_access_iterator

    displayIteratorCategory(set<int>::iterator());	//bidirectional_iterator
    displayIteratorCategory(map<int,int>::iterator()); //bidirectional_iterator
    displayIteratorCategory(unordered_set<int>::iterator()); //forward_iterator
    displayIteratorCategory(unordered_map<int,int>::iterator()); //forward_iterator

    displayIteratorCategory(istream_iterator<int>()); //input_iterator
    displayIteratorCategory(ostream_iterator<int>(cout,"")); //output_iterator
}

Containers vector, array and deque are continuous and jumping spaces for users, so iterators are random_access_iterator.

The container list is a two-way linked list. set, map, multimap and multiset are ordered and support two-way movement, so it is bidirectional_iterator.

Container forward_list is a one-way linked list container unordered_set,unordered_map,unordered_multiset,unordered_ Each bucket in the map hash table is a one-way linked list. Therefore, its iterator can only move in one direction, so it is forward_iterator type.

Iterator istream_iterator and ostream_iterator is essentially an iterator, and the source code of these two classes will be mentioned later.

4.2 influence of iterator on Algorithm

Most algorithms in STL will call different overloaded functions according to the type of iterator and other information, and call the optimal overloaded version for a specific iterator.

  • distance in STL depends on different iterators_ Category performs different overloaded functions
template<class InputIterator>
inline iterator_traits<InputIterator>::difference_type
_distance(InputIterator first,InputIterator last,input_iterator_tag){
	iterator_traits<InputIterator>::difference_type n=0;
    while(first!=last){
        ++first;
        ++n;
    }
    return n;
}

template<class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
_distance(RandomAccessIterator first,RandomAccessIterator last,random_access_iterator_tag){
return last-first;}


//Iterator here_ traits<InputIterator>::difference_ Type is the return type of the function. The type is determined only at run time
template<class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first,InputIterator last){
    iterator_traits<InputIterator>::iterator_category category;
}
  • Advance in STL depends on different iterators_ Category performs different overloaded functions
templaye<class BidirectionalIterator,class Distance>
inline void _advance(BidirectionalIterator&i,Distance n,bidirectional_iterator_tag){
    if(n>=0){
		while(n--)
            ++i;
    }
    else{
		while(n++)
            --i;
    }
}


templaye<class RandomAccessIterator,class Distance>
inline void _advance(RandomAccessIterator&i,Distance n,random_access_iterator_tag){
    i+=n;
}


template<class InputIterator,class Distance>
inline void advance(InputIterator& i,Distance n){
	_advance(i,n,iterator_category(i));
}

template<class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&){
    typedef typename iterator_traits<Iterator>::iterator_category category;
    return category(); //Create temp category object
}
  • In STL, copy is based on different iterators_ Category and type traits perform different overloaded functions

  • destroy in STL depends on different iterators_ Category and type traits perform different overloaded functions

  • STL algorithms are all template functions and cannot be used for the incoming iterator_ The category type is qualified, but the template parameter name in the source code is still the received iterator_category gives some hints. For example, on named template parameters.
template<class InputIterator>
inline Iterator_traits<InputIterator>::diffrence_type
distance(InputIterator first,InputIterator last){
...
}

template<class ForwardIterator>
inline void rotate(ForwardIterator first,ForwardIterator middle,ForwardIterator last){
...
}

template<class RandomAccessIterator>
inline void sort(RandomAccessIterator first,RandomAccessIterator last){
...
}

template<class InputIterator,class T>
InputIterator find(InputIterator first,InputIterator last,const T&value){
...
}

4.3 algorithm source code analysis

  • accumulate function

The default operation of the algorithm calculate is +, but the overloaded version allows user-defined operation and supports all containers. The source code is as follows:

template<class InputIterator,class T>
T accumulate(InputIterator first,InputIterator last,T init){
	for(;first!=last;++first)
		//Accumulate elements on init
		init+=*first;
	return	init;
}

template<class InputIterator,class T,class BinaryOperation>
T accumulate(InputIterator first,Input last,T init,BinaryOperation binary_op){
    for(;first!=last;++first)
        init = binary_op(*first,init);
    return init;
}

The test procedure is as follows:

template<typename T>
T myfuncminus(const T &x,const T& y) {
    return x - y;
}

struct myobjectminus {
    int operator()(const int& x, const int& y) {
        return x - y;
    }
};

void accumulateTest() {
    int arr[] = { 10,20,30 };
    cout << "using default accumulate:";
    cout<<accumulate(arr, arr + 3, 100); //160
    cout << "\n";

    cout << "using functional's minus:";
    cout<<accumulate(arr, arr + 3,100,minus<int>());	//40
    cout << "\n";

    cout << "using custom functino:";
    cout<<accumulate(arr, arr + 3,100, myfuncminus<int>);	//40
    cout << "\n";

    cout << "using cunston object:";
    cout<<accumulate(arr, arr + 3,100, myobjectminus());	//40
}
  • for_each traversal function

for_each source code

template<class InputIterator,class Function>
Function for_each(InputIterator first,InputIterator last,Function f)
{
    for(;first!=last;++first){
        f(*first);
    }
    return f;
}

The test procedure is as follows:

template<typename T>
void myforFunc(const T& x) {
	cout << " " << x;
}

class myforClass {
public:
	void operator()(const int& x)const {
		cout << " " << x;
		}
};

void foreachTest() {
	vector<int>v(5, 10);
	for_each(v.begin(), v.end(), myforFunc<int>);
	cout << endl;
	for_each(v.begin(), v.end(), myforClass());
}
  • replace,replace_if,replace_copy

replace source code

template<class ForwardIterator,class T>
void replace(ForwardIterator first,ForwardIteratorlast,const T& old_value,const T& new_value){
//All in the range are equivalent to old_ The element of value is marked with new_value strip
	for(;first!=last;++first)
		if(*first==old_value){
			*first=new_value;
		}
}

replace_if source code

template<class ForwardIterator,class Predicate,class T>
void replace_if(ForwardIterator first,ForwardIterator last,Predicate pred,const T& new_value){
//All elements in the scope that satisfy pred() as true are marked with new_value substitution
	for(;first!=last;++first){
		if(pred(*first))
			*first=new_value;
	}
}

replace_copy source code

template<class InputIterator,class OutputIterator,class T>
outputIterator replace_copy(InputIterator first,InputIterator last,OutputIterator result,const T& old_value,const T& new_value){
//All in the range are equivalent to old_ All values are marked with new_ Put value in the new range,
//The original value of the non conformers shall be put into the new area room
	for(;first!=last;++first,++result)
		*result=*first!=old_value?new_value:*first;
	return result;
}
  • count,count_if

cout source code

template<class InputIterator,class T>
typename iterator_traits<InputIterator>::difference_type
count(InputIterator first,InputIterator last,const T& value){
	//Define a counter with an initial value of 0 n
	typename iterator_traits<InputIterator>::differebce_type n =0;
	for(;first!=last;++first)
		if(*first==value)
			++n;
	return n;
}

count_if source code

template<class InputIterator,class Predicate>
typename iterator_traits<InputIterator>::difference_type
count(InputIterator first,InputIterator last,Predicate pred){
	//Define a counter with an initial value of 0 n
	typename iterator_traits<InputIterator>::differebce_type n =0;
	for(;first!=last;++first)
        //If the result brought into pred by the element is true, the counter is accumulated by 1
		if(pred(*first))
			++n;
	return n;
}

Container without member count algorithm:

array,vector,list,forward_list,deque

Container with member function count algorithm (associative container has more efficient counting method)

set\multiset,map\multimap,unordered_set\multi,unordered_map\multi

  • find,find_if sequential search algorithm

find source code

template<class InputIterator,class T>
InputIterator find(InputIterator first,InputIterator last,const T& value)
{
	while(first!=last&&*first!=value)
		++first;
	return first;
}

find_id source code

template<class InputIterator,class Predicate>
InputIterator find(InputIterator first,InputIterator last,Predicate pred)
{
	while(first!=last&&!pred(*first))
		++first;
	return first;
}

Container without member find algorithm:

array,vector,list,forward_list,deque

Container with member function find algorithm (associative container has more efficient search method)

set\multiset,map\multimap,unordered_set\multi,unordered_map\multi

  • sort sorting algorithm

The algorithm sort implies that the parameter is random_access_iterator_tag type iterators, so the algorithm only supports containers array, vector and deque

Container list and forward_list contains the sort method

The containers set, map, multiset and multimap are ordered, and the containers are unordered_set,unordered_map,unordered_multiset and unordered_ The map itself is unordered and does not need sorting

template<typename RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last)
{
    // ...
}

Application:

// Custom function
bool myfunc(int i, int j) { return (i < j); }

// Custom imitation function
struct myclass {
    bool operator()(int i, int j) { return (i < j); }
} myobj;

int main() {
    int myints[] = {32, 71, 12, 45, 26, 80, 53, 33};
    vector<int> myvec(myints, myints + 8);          // Elements in myvec: 32 71 12 45 26 80 53 33

    sort(myvec.begin(), myvec.begin() + 4);         // Use the defau lt ` < 'operation to define the order. The elements in myvec: (12 32 45 71) 26 80 53 33
    sort(myvec.begin() + 4, myvec.end(), myfunc); 	// Use custom functions to define the order. Elements in myvec: 12 32 45 71 (26 33 53 80)
    sort(myvec.begin(), myvec.end(), myobj);     	// Define the order using a custom functor, and the elements in myvec: (12 26 32 33 45 53 71 80)
    sort(myvec.rbegin(), myvec.rend());				// Reverse order using reverse iterator, elements in myvec: 80 71 53 45 33 32 26 12
    return 0;
}
  • binary_search

Binary algorithm_ Search finds the element value from the ordered interval, and supports all sortable containers

Binary algorithm_ Search internally calls the algorithm lower_bound, use binary search method to query elements

Algorithm lower_bound and upper_bound returns the first and last insertable positions of corresponding elements respectively

template<class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last, const T &val) {
    first = std::lower_bound(first, last, val);		// Internal call lower_bound
    return (first != last && !(val < *first));
}

template<class ForwardIterator, class T>
ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last, const T &val) {
    ForwardIterator it;
    typename iterator_traits<ForwardIterator>::difference_type count, step;
    count = distance(first, last);
    while (count > 0) {
        it = first;
        step = count / 2;
        advance(it, step);
        if (*it < val) { // or: if (comp(*it,val)) for version (2)
            first = ++it;
            count -= step + 1;
        } else
            count = step;
        return first;
    }
}

5. Imitation function

Functions is for Algorithm. Functions is a class that overloads the operator().

The condition that makes the function adaptable, that is, if the function answers the questions raised by the adapter, it must inherit from unary_function and binary_function.

template<class Arg,class Result>
struct unary_function{
	typedef Arg argument_type
	typedef Result result_type
};

template<class Arg1,class Arg2,class Result>
struct binary_function{
	typedef Arg1 first_argument_type;
	typedef Arg2 second_argument_type;
	typedef Result result_type
}
template<class T>
struct greater:public binary_function<T,T,bool>{
	bool operator()(const T& x,const T& y)const{
		return x>y;
	}
}


template<class T>
struct less:public binary_function<T,T,bool>{
	bool operator()(const T& x,const T& y)const{
		return x<y;
	}
}

6. Adapter

6.1 container adapter

  • Container stack and queue are adapters for container deque.
  • Containers set, map, multiset, and multimap are containers rb_tree adapter.
  • Container unordered_set,unordered_map,unordered_multiset,unordered_multimap is an adapter for container hashtable.

6.2 emulator adapter

  • The binder2nd functor adapter can bind the second parameter of a binary functor.

Test procedure

cout << count_if(vi.begin(),vi.end (),bind2nd(less<int>(), 40));

The source code is as follows:

template<class Operation,class T>
inline binder2nd<Operation>
bind2nd(const Operation& op,const T& x){
    typedef typename Operation::second_argument_type arg2_type //The operation passed in requires the basic inheritance binary_function, there will be second_argument_type
  	return binder2nd<Operation>(op, arg2_type(x)); //Create an anonymous binder2nd object and store op and arg2 values in binder2nd. arg2_type is also asking, and x is the second of the operation_ argument_ Whether the type of type is the same.
}

template<class Operation>
class binder2nd:public unary_function<typename Operation::first_argument_type,typename Operation::result_type>
{
protected:
    Operation op; //operation
    typename Operation::second_argument_type value; //Second argument
 public:
    //Constructor, record the operation and the second argument
    binder2nd(const Operation& x,const typename Operation::second_argument_type& y):op(x),value(y){};
    
    //Overload () operator
    typename Operation::result_type
    Operator()(const typename Operation::frist_argument_type& x)const{ //Called by pred(*first) at runtime
        return op(x,value); //Take the passed in X as the first parameter of op and value as the second parameter, that is, the adaptation operation of binding X in bind2nd(op,x) as the second parameter of function is realized
    }
}

template<class Iterator,class Predicate>
typename iterator_traits<Iterator>::difference_type
count_if(Iterator first,Iterator last,Predicate pred){
    typename iterator_traits::difference_type n=0;
    for(;first!=last;++first)
        if(pred(*first))
            ++n;
    return n;
}
  • not1 Reverse Adapter

Test procedure:

//Test procedure
cout<<count_if(v1.begin(),v2.end(),not1(bind2nd(less<int>(),50)))

The source code is as follows:

//Auxiliary functions make it easier for user to use unary_ negate<Pred>
template<class Predicate>
inline unary_negate<Predicate> not1(const Predicate& pred){
	return unary_negate<Predicate>(pred);
}

//The following is the logical complex value of an adaptive predicate
template<class Predicate>
class unary_negate:public unary_function<typename Predicate::argument_type,bool>
{
protected:
	Predicate pred; //Operation of internal member store
public:
    explicit unary_neget(const Predicate& x):pred(x){};
    bool operator()(const typename Predicate::argument_type& x)const{
        return !pred(x);	//Take the inverse value stored in the operation logic, and the transmitted x needs to be transmitted to the operation logic.
	}
}

template<class Iterator,class Predicate>
typename iterator_traits<Iterator>::difference_type
count_if(Iterator first,Iterator last,Predicate pred){
    typename iterator_traits::difference_type n=0;
    
    for(;first!=last;++first)
        if(pred(*first))
            ++n;        
    return n;
}
  • Bind in C++11, the classes binder1st and binder2nd used to bind function parameters and their auxiliary functions bind1st and bind2nd are replaced with more powerful bind

Function bind and placeholder in namespace std::placeholders_ 1,_ 2,_ 3... And other placeholders can be bound by using the. Bind function:

  • Function, function (functions and function objects)
  • Member function
  • Data member (member variable)

The demonstration procedure is as follows:

using namespace std::placeholders;

inline double mydividefunc(const double& x, const double& y) {
    return x / y;
}

struct mydivideobject {
    double a, b;
    double operator()() {  //member function has an argument this
        return a * b;
    }
}myobject;


void bind11Test() {

    //binding functions
    auto fn_five = bind(mydividefunc, 10, 2); //10/2 5
    cout << fn_five()<< "\n";


    auto fn_half = bind(mydividefunc, _1, 2); //return x/2
    cout << fn_half(10) << "\n";	//5

    auto fn_invert = bind(mydividefunc, _2, _1); //return x / y
    cout << fn_invert(10, 2) << "\n";	//0.2

    auto fn_rounding = bind<int>(mydividefunc, _1, _2); //return int(x/y)
    cout << fn_rounding(10, 3) << endl; //3

    //binding members
    mydivideobject ob{ 10,2 };

    auto bound_memfn = bind(&mydivideobject::operator(), _1);		//return x.operator()
    cout << bound_memfn(ob) << endl;		//20


    auto bound_memdata = bind(&mydivideobject::a, ob);	//return ab.a
    cout << bound_memdata() << endl;	//10


    vector<int>v{ 10,20,30,40,20,10,50,90 };
    int n = count_if(v.cbegin(), v.cend(), not1(bind2nd(less<int>(), 50)));
    cout << "Number greater than or equal to 50,n=" << n << endl;


    auto fn_ = bind(less<int>(), _1, 50);
    n = count_if(v.cbegin(), v.cend(), fn_);
    cout << "(bind11)Number less than 50,n=" << n << endl;

    cout << count_if(v.cbegin(), v.cend(), bind(less<int>(), _1, 50)) << endl;
}

6.3 iterator adapter

  • Reverse iterator reverse_iterator

The direction of the inverse iterator is opposite to the original iterator. The head (tail) of the inverse iterator is the tail (head) of the forward iterator, so the addition and subtraction operations are the opposite.

reverse_ The source code of the iterator is as follows:

template<class Iterator>
class reverse_iterator{
protected:
	Iterator current //Corresponding forward iterator
public:        
    //The five association types of reverse iterators are the same as those of forward iterators
    typedef typename iterator_traits<Iterator>::iterator_category iterator_category;
    typedef typename iterator_traits<Iterator>::value_type value_type;
    ...
    typedef Iterator iterator_type; //Forward iterator type
    typedef reverse_iterator<Iterator> self; //Reverse iterator type
    
public:
    explicit reverse_iterator(iterator_type x):current(x){};
    reverse_iterator(const self& x):current(x.current){};
    iterator_type base()const{return current;}
    
    //Reverse iterator value: the iterator is regarded as a forward iterator, and then the value is taken again
    reference operator*()const{
        Iterator tmp = current;
        return *--tmp;
	}
    
    pointer operator->()const{
        Iterator tmp = current;
        return &(operator*());
	}
    
    //The addition operation of reverse iteration corresponds to the subtraction operation of forward iterator
    self& operator++(){--current;return *this;}
    self& operator--(){++current;return *this;}
    self operator+(difference_type n){return self(current-n);}
    self operator-(difference_type n){return self(current+n);}
}
  • Insert iterator inserter_interator

    Iterator used to generate in-situ insertion operation, using insert_ When the iterator iterator inserts an element, it pushes the original position element back.

    When we talk about this iterator, we need to look at a program to introduce this topic.

    int myints[] = { 10,20,30,40,50,60,70};
    vector<int>myvec(7);
    copy(myints, myints + 7, myvec.begin());
    

    Here, the copy function is used to copy the elements in myints into myvec. However, by looking at the copy source code, we can see that the so-called copy is just an assignment each time. In addition, seven positions are reserved for vector. If you assign values without reservation, a series of problems will be caused.

    copy source code:

    template<class InputIterator,class OutputIterator>
    OutputIterator copy(InputIterator firts,InputIterator last,OutputIterator result)
    {
        while(first!=last){
            *result=*first;
            ++result;
            ++first;
    	}
        return result;
    }
    

    So here we introduce a second example to illustrate insert_ Usage of iterator:

    vector<int>foo, bar;
    for (int i = 1; i <= 5; i++) {
    	foo.push_back(i);
    	bar.push_back(i * 10);
    }
    auto ite = foo.begin();
    copy(bar.begin(), bar.end(), inserter(foo, ite + 3));
    

    The results are shown in the figure below:

Insert_iterator source code:

//Auxiliary function to help user use insert_iterator
template<class Container,class Iterator>
inline insert_iterator<Container>
inserter(Container& x,Iterator i){
    typedef typename Container::iterator iter;
    return insert_iterator<Container>(x,iter(i));
}

template<class Container>
class insert_iterator{ 
protected:
    Container* container; //Bottom container
    typename Container::iterator iter;//iterator 
public:
    typedef output_iterator_tag iterator_type; //iterator types 
    
    //Constructors, storage containers, and iterators
    insert_iterator(Container&x,typename Container::iterator i):container(&x),iter(i){}
    
    insert_iterator<Container>&
    operator=(const typename Container::value_type& value){
        iter = container->insert(iter,value); //insert() for key transfer
        ++iter; //Make the insert iterator always move close to its target
        return *this;
    }
    
    //Overloaded operators * and + + do nothing
    insert_iterator<Container> &operator*() { return *this; }
    insert_iterator<Container> &operator++() { return *this; }
    insert_iterator<Container> &operator++(int) { return *this; }
}

//	Therefore, the overload = operator plays a decisive role here
//    while(first!=last){
//      *result=*first;
//        ++result;
//        ++first;
  • Output iterator ostream_interator

Output stream iterator ostream_iterator is often used to encapsulate std::cout. The following program outputs the elements in the container to std::cout:

vector<int>v;
for (int i = 1; i <= 10; i++) 
	v.push_back(i * 10);
ostream_iterator<int>out_it(cout, ",");
copy(v.begin(), v.end(), out_it);	//10,20,30,40,50,60,70,80,90,100

ostream_iterator source code:

template<class T,
		 class charT=char,
		 class traits=char_traits<charT>>
class ostream_iterator:public iterator<output_iterator_tag,void,void,void,void>
{
	basic_ostream<charT,traits>* out_stream;
	const charT* delim;	//Spacer
public:
	typedef charT char_type;
	typedef traits traits_type;
	typedef basic_ostream<charT,traits> ostream_type;
	
    //Constructor
    ostream_iterator(ostream_type&s):out_stream(&s),delim(0){}
    ostream_iterator(ostream_type&s,const charT* delimiter):out_stream(&s),delim(delimiter){}
    ostream_iterator(const ostream_iterator<T,charT,traits>&x):out_stream(x.out_stream),delim(x.delim){}
    
    ~ostream_iterator(){}
    
    //operator = overload key
    ostream_iterator<T,charT,traits>& operator=(const T& value){
        *out_stream<<value;
        if(delim!=0)
        *out_stream<<delim;
        return *this;
    }
    
    // Overload operators * and + +: do nothing
    ostream_iterator<T, charT, traits> &operator*() { return *this; }
    ostream_iterator<T, charT, traits> &operator++() { return *this; }
    ostream_iterator<T, charT, traits> &operator++(int) { return *this; }
}
  • Input iterator istream_interator

Input stream iterator istream_iterator is used to encapsulate std::cin. Example 1:

double val1, val2;
cout << "Please,insert two values:";
istream_iterator<double>eos;	//end of string
istream_iterator<double>iit(cin);	//Equivalent to CIN > > value
if (iit != eos)
	val1 = *iit;
++iit;
if (iit != eos)
	val2 = *iit;
cout << val1 << "*" << val2 << "=" << (val1 * val2) << endl;

istream_iterator source code:

template<class T,
		 class charT=char,
		 class traits=char_traits<charT>,
		 class Distance=ptrdiff_t>
class istream_iterator:public iterator<input_iterator_tag,T,Distance,const T*,const T&>{
    
    basic_istream<charT,traits>*in_stream;
    T value;
    
public:
    typedef charT char_type;
	typedef traits traits_type;
	typedef basic_istream<charT,traits> istream_type;
    
    //Constructor
    istream_iterator():in_stream(0){}
    istream_iterator(istream_type&s):in_stream(&s)){++*this;}
    istream_iterator(const istream_iterator<T,charT,traits,Distance>&x):in_stream(x.in_stream),valye(x.value){}
    ~istream_iterator(){};
    
    //Operator * - > overload
    const T& operator*()const{return value};
    const T* operator->()const{return &value};
    
    istream_iterator<T,charT,traits,Distance>&
    operator++(){	//Once created, the user is immediately asked to enter
        if(in_stream&&!(*in_stream>>value))
            in_stream=0;
        return *this;
    }
    
    istream_iterator<T,charT,traits,Distance>
    operator++(int){
        istream_iterator<T,charT,traits,Distance>temp = *this;
        ++*this;
        return temp;
    }
}

Example 2:

istream_iterator<int>iit(cin),eos;
copy(iit,eos,inserter(c,c.begin()));

7. Other standard library contents

7.1 a universal Hash Function

Using unordered_ During set \ map, we need to customize the hash function. The so-called hashfunction is to make the generated hashcode s as disorderly as possible. Is there a universal hash function template?

How to create hashfunction:

  • 1. Function object
class Customer{
	...
}

class CustomerHash{

public:
	std:size_t operator()(const Customer& c)const{
		return ... //Need to customize
	}	
}

unordered_set<Customer,CustomerHash> custset
  • 2. Function
size_t customer_hash_func(const Customer& c){
	return ...//custom
};

unordered_set<Customer,size_t(*)(const Customer&)>
custset(20,customer_hash_func);
  • 3. Provide partial specialized version
template< typename T,
		  typename Hash = hash<t>,
		  typename Eqpred = equal_to<T>,
		  typename Allocator = allocator<t>>
class unordered_set;
class MyString{
private:
	char* _date;
	size_t _len;
    ...
};

namespace std //Must be placed in std
{
    template<>
    struct hash<Mystring> //Partial specialized version provided by yourself
    {
        size_t operator()(const Mystring& s)const onexcept
        {
            return hash<string>()(string(s.get());) //Borrow hash < string >
        }
    }
}

Universal hashfunction template

class CustomerHash{
public:
	std:size_t operator()(const Customer& c)const{
		return hash_val(c.fname,c.lname,c.no);
	}
}

//The generalized version called first
template<typename...Types>
inline size_t hash_val(const Types&...args){
    size_t seed = 0;
    hash_val(seed,args...);
    return seed;
}

template<typename T,typename ...Types>
inline void hash_val(size_t& seed,const T&val,const Types& ...args){
	hash_combine(seed,val);
    hash_val(seed,args...);
}

template<typename T>
inline void hash_combine(size_t& seed,const T&val){
    seed^=std::hash<T>()(val)+0x9e3779b9+(seed<<6)+(seed>>2); //0x9e3779b9 is the golden ratio
}

template<typename T>
inline void hash_val(size_t& seed,const T& val){
	hash_combine(seed,val);
}

7.2 tuple use case

// Create tuple
tuple<string, int, int, complex<double> > t;
tuple<int, float, string> t1(41, 6.3, "nico");	// Specify initial value
auto t2 = make_tuple(22, 44, "stacy");			// Using make_ The tuple function creates a tuple

// Use the get < > () function to get the elements in the tuple
cout << "t1:" << get<0>(t1) << "<< get<1>(t1)<<" << get<2>(t1) << endl;
get<1>(t1) = get<1>(t2);		// The obtained element is an lvalue and can be assigned a value


// tuple can be compared directly
if (t1 < t2) { 
    cout << "t1 < t2" << endl;
} else {
    cout << "t1 >= t2" << endl;
}

// Construction can be copied directly
t1 = t2; 

// Use tie function to bind tuple elements to variables
tuple<int, float, string> t3(77, 1.1, "more light");
int i1, float f1; string s1;
tie(i1, f1, s1) = t3; 

// Infer tuple type
typedef decltype(t3) TupleType;		// It is inferred that the type of t3 is tuple < int, float, string >

// Use tuple_size gets the number of elements
cout << tuple_size<TupleType>::value << endl; 		// 3
// Use tuple_element get element type
tuple_element<1, TupleType>::type fl = 1.0; 		// float
  • tuple source code analysis
// Define tuple class
template<typename... Values>
class tuple;

// Specialization template parameter: null
template<>
class tuple<> {};

// Specialized template parameters
template<typename Head, typename... Tail>
class tuple<Head, Tail...> :
        private tuple<Tail...>        	// Tuple class inherits from tuple class. The parent class is similar to the child class without a template parameter
{
    typedef tuple<Tail...> inherited;	// Parent class type  
protected:
    Head m_head;						// Save the value of the first element
public:
    tuple() {}
    tuple(Head v, Tail... vtail)		// Constructor: assign the first element to m_head, use other elements to build the parent tuple
		: m_head(v), inherited(vtail...) {}

    Head head() { return m_head; }		// Returns the value of the first element
    inherited& tail() { return *this; }	// Returns the tuple composed of the remaining elements (cast the current element to the parent type)
};

Calling the head function returns the element M_ The value of head

Call the tail function to return the starting point of the parent class component, convert the current tuple to the parent class tuple through coercion, and discard the element M_ Memory occupied by head

7.3 type traits

type traits, a type extraction mechanism, obtains information related to classes. There are different implementations before C++11 and C++11.

  • Before C++11

Before C++11, typetraits were created by__ type_ For the implementation of traits, each time we create a class, we need to specialize one with the class as a template parameter__ type_traits class

template<class type>
struct __type_traits{
	typedef __false_type has_trivial_default_constructor;	//Is the default constructor negligible
	typedef __false_type has_trivial_copy_constructor;		//Is the copy constructor negligible
	typedef __false_type has_trivial_assignment_operator;	//Whether the assignment function can be ignored
	typedef __false_type has_trivial_destructor;			//Is the destructor negligible
	typedef __false_type is_POD_type						//Is it POD(plain old data) type
};

template<>
struct __type_traits<int>{
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

template<>
struct __type_traits<double> {
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};
  • C++11

C++11 in header file type_traits introduces a series of auxiliary classes. These auxiliary classes can automatically obtain the basic information of the class according to the passed template parameters and realize type extraction. We don't need to manually write type extraction information for the class we create.

The following example shows the application of type extraction mechanism:

cout << "is_ void\t" << is_void<T>::value << endl;
cout << "is_ integral\t" << is_integral<T>::value << endl;
cout << "is_ floating point\t" << is_floating_point<T>::value << endl;
...
  • Source code analysis

Header file type_ The auxiliary class remove is defined in traits_ Const and remove_volatile is used to remove const and volatile keywords from a type

// remove const
template<typename _Tp>
struct remove_const {
    typedef _Tp type;
};
template<typename _Tp>
struct remove_const<_Tp const> {
    typedef _Tp type;
};

// remove volatile
template<typename _Tp>
struct remove_volatile {
    typedef _Tp type;
};
template<typename _Tp>
struct remove_volatile<_Tp volatile> {
    typedef _Tp type;
};

is_void class inherits from__ is_void_helper class__ is_void_ The helper class uses partial specialization to determine whether the passed in template parameter is void

template<typename>
struct __is_void_helper
	: public false_type {
};

template<>
struct __is_void_helper<void>
	: public true_type {
};

template<typename _Tp>
struct is_void
	: public __is_void_helper<typename remove_cv<_Tp>::type>::type {
};

is_ The integral class inherits from__ is_integral_ The helper class also uses partial specialization to determine whether the passed template parameter is of integer type

template<typename>
struct __is_integral_helper : public false_type { };

template<> struct __is_integral_helper<bool> : public true_type { };
template<> struct __is_integral_helper<char> : public true_type { };
template<> struct __is_integral_helper<signed char> : public true_type { };
template<> struct __is_integral_helper<unsigned char> : public true_type { };
// ...

template<typename _Tp>
struct is_integral
	: public __is_integral_helper<typename remove_cv<_Tp>::type>::type { };

Some type traits auxiliary classes (such as is_enum, is_union and is_class) are implemented by the compiler, and their implementation functions cannot be found in the STL source code

// is_enum
template<typename _Tp>
struct is_enum
	: public integral_constant<bool, __is_enum(_Tp)>    // __ is_ The enum function is implemented by the compiler, and its source code cannot be found in the STL source code
{ };

// is_union
template<typename _Tp>
struct is_union
	: public integral_constant<bool, __is_union(_Tp)>    // __ is_ The union function is implemented by the compiler, and its source code cannot be found in the STL source code
{ };

// is_class
template<typename _Tp>
struct is_class
	: public integral_constant<bool, __is_class(_Tp)>    // __ is_ The class function is implemented by the compiler, and its source code cannot be found in the STL source code
{ };

7.4 cout

Why can cout output multiple data types? Is cout a class or an object?

  • G2.9 iostream.h
class _IO_ostream_withassign:public ostream{
public:
	_IO_ostream_withassign& operator=(ostream&);
	_IO_ostream_withassign& operator=(_IO_ostream_withassign& rhs){
		return operator=(static_cast<ostream&>(rhs));
	};
	extern _IO_ostream_withassign cout; //cout is an object
}
class ostream:virtual public ios
{
public:
    ostream& operator<<(char c);
    ostream& operator<<(unsigned char c){return (*this)<<(char)c;}
    ostream& operator<<(signed char c){return (*this)<<(char)c;}
    ostream& operator<<(const char* s);
    ostream& operator<<(const unsigned char* c){return (*this)<<(const char*)s;}
    ostream& operator<<(const signed char* c){return (*this)<<(const char*)s;}
    ostream& operator<<(int n);
    ostream& operator<<(unsigned int n);
    ostream& operator<<(long n);
    ostream& operator<<(unsigned long n);
   ...
}

Tags: C++ STL UE4 Cpp

Posted on Mon, 29 Nov 2021 14:43:18 -0500 by peppino