Desde la perspectiva general, puede pensar deque
como undouble-ended queue
Los datos en deque
son almacenados por fragmentos de vector de tamaño fijo, que son
señalado por un map
(que también es un fragmento de vector, pero su tamaño puede cambiar)
El código de la parte principal del deque iterator
es el siguiente:
/*
buff_size is the length of the chunk
*/
template <class T, size_t buff_size>
struct __deque_iterator{
typedef __deque_iterator<T, buff_size> iterator;
typedef T** map_pointer;
// pointer to the chunk
T* cur;
T* first; // the begin of the chunk
T* last; // the end of the chunk
//because the pointer may skip to other chunk
//so this pointer to the map
map_pointer node; // pointer to the map
}
El código de la parte principal del deque
es el siguiente:
/*
buff_size is the length of the chunk
*/
template<typename T, size_t buff_size = 0>
class deque{
public:
typedef T value_type;
typedef T& reference;
typedef T* pointer;
typedef __deque_iterator<T, buff_size> iterator;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
typedef pointer* map_pointer;
// allocate memory for the chunk
typedef allocator<value_type> dataAllocator;
// allocate memory for map
typedef allocator<pointer> mapAllocator;
private:
//data members
iterator start;
iterator finish;
map_pointer map;
size_type map_size;
}
A continuación le daré el código central de deque
, principalmente sobre tres partes:
iterador
¿Cómo construir un deque
1. iterador ( __deque_iterator
)
El principal problema del iterador es, cuando ++, - iterator, puede saltar a otro fragmento (si apunta al borde del fragmento). Por ejemplo, hay tres fragmentos de datos: chunk 1
, chunk 2
, chunk 3
.
Los pointer1
punteros al comienzo de chunk 2
, cuando el operador --pointer
apunta al final de chunk 1
, con el fin de pointer2
.
A continuación daré la función principal de __deque_iterator
:
En primer lugar, salta a cualquier fragmento:
void set_node(map_pointer new_node){
node = new_node;
first = *new_node;
last = first + chunk_size();
}
Tenga en cuenta que, la chunk_size()
función que calcula el tamaño del fragmento, puede pensar que devuelve 8 para simplificar aquí.
operator*
obtener los datos en el fragmento
reference operator*()const{
return *cur;
}
operator++, --
// formas de incremento de prefijo
self& operator++(){
++cur;
if (cur == last){ //if it reach the end of the chunk
set_node(node + 1);//skip to the next chunk
cur = first;
}
return *this;
}
// postfix forms of increment
self operator++(int){
self tmp = *this;
++*this;//invoke prefix ++
return tmp;
}
self& operator--(){
if(cur == first){ // if it pointer to the begin of the chunk
set_node(node - 1);//skip to the prev chunk
cur = last;
}
--cur;
return *this;
}
self operator--(int){
self tmp = *this;
--*this;
return tmp;
}
Saltar iterador n pasos / acceso aleatorio
self& operator+=(difference_type n){ // n can be postive or negative
difference_type offset = n + (cur - first);
if(offset >=0 && offset < difference_type(buffer_size())){
// in the same chunk
cur += n;
}else{//not in the same chunk
difference_type node_offset;
if (offset > 0){
node_offset = offset / difference_type(chunk_size());
}else{
node_offset = -((-offset - 1) / difference_type(chunk_size())) - 1 ;
}
// skip to the new chunk
set_node(node + node_offset);
// set new cur
cur = first + (offset - node_offset * chunk_size());
}
return *this;
}
// skip n steps
self operator+(difference_type n)const{
self tmp = *this;
return tmp+= n; //reuse operator +=
}
self& operator-=(difference_type n){
return *this += -n; //reuse operator +=
}
self operator-(difference_type n)const{
self tmp = *this;
return tmp -= n; //reuse operator +=
}
// random access (iterator can skip n steps)
// invoke operator + ,operator *
reference operator[](difference_type n)const{
return *(*this + n);
}
2. Cómo construir un deque
función común de deque
iterator begin(){return start;}
iterator end(){return finish;}
reference front(){
//invoke __deque_iterator operator*
// return start's member *cur
return *start;
}
reference back(){
// cna't use *finish
iterator tmp = finish;
--tmp;
return *tmp; //return finish's *cur
}
reference operator[](size_type n){
//random access, use __deque_iterator operator[]
return start[n];
}
template<typename T, size_t buff_size>
deque<T, buff_size>::deque(size_t n, const value_type& value){
fill_initialize(n, value);
}
template<typename T, size_t buff_size>
void deque<T, buff_size>::fill_initialize(size_t n, const value_type& value){
// allocate memory for map and chunk
// initialize pointer
create_map_and_nodes(n);
// initialize value for the chunks
for (map_pointer cur = start.node; cur < finish.node; ++cur) {
initialized_fill_n(*cur, chunk_size(), value);
}
// the end chunk may have space node, which don't need have initialize value
initialized_fill_n(finish.first, finish.cur - finish.first, value);
}
template<typename T, size_t buff_size>
void deque<T, buff_size>::create_map_and_nodes(size_t num_elements){
// the needed map node = (elements nums / chunk length) + 1
size_type num_nodes = num_elements / chunk_size() + 1;
// map node num。min num is 8 ,max num is "needed size + 2"
map_size = std::max(8, num_nodes + 2);
// allocate map array
map = mapAllocator::allocate(map_size);
// tmp_start,tmp_finish poniters to the center range of map
map_pointer tmp_start = map + (map_size - num_nodes) / 2;
map_pointer tmp_finish = tmp_start + num_nodes - 1;
// allocate memory for the chunk pointered by map node
for (map_pointer cur = tmp_start; cur <= tmp_finish; ++cur) {
*cur = dataAllocator::allocate(chunk_size());
}
// set start and end iterator
start.set_node(tmp_start);
start.cur = start.first;
finish.set_node(tmp_finish);
finish.cur = finish.first + num_elements % chunk_size();
}
Supongamos que i_deque
tiene 20 elementos int 0~19
cuyo tamaño de fragmento es 8 y ahora push_back 3 elementos (0, 1, 2) para i_deque
:
i_deque.push_back(0);
i_deque.push_back(1);
i_deque.push_back(2);
Es la estructura interna como a continuación:
Luego push_back nuevamente, invocará asignar nuevo fragmento:
push_back(3)
Si lo hacemos push_front
, asignará un nuevo fragmento antes del anteriorstart
Tenga en cuenta que cuando el push_back
elemento está en deque, si todos los mapas y fragmentos están llenos, se asignará un nuevo mapa y se ajustarán los fragmentos. Pero el código anterior puede ser suficiente para que usted lo entienda deque
.