std::deque的简化源码详解
1. 基本结构与成员变量
源码片段:
template <typename T, typename Alloc = std::allocator<T>>
class deque {
private:static const size_type block_size = 16; // 每个块的大小using block_type = T*; // 块类型using map_type = std::vector<block_type>; // 映射类型map_type map; // 存储块指针的映射size_type map_size; // 映射大小size_type start; // 第一个元素的块索引size_type finish; // 最后一个元素的块索引size_type first; // 第一个元素在块中的索引size_type last; // 最后一个元素在块中的索引Alloc alloc; // 分配器
};
详解:
block_size
:每个内存块固定容纳 16 个元素,这是deque
分块存储的基础。block_type
:每个块是一个T*
类型的指针,指向一块连续内存。map_type
:使用std::vector
存储所有块的指针,形成映射表。map
:核心数据结构,管理所有块的指针。map_size
:映射表当前的大小。start
和finish
:分别标记首块和尾块在map
中的位置。first
和last
:首块和尾块中元素的具体偏移量。alloc
:内存分配器,用于动态管理内存。
2. 构造函数
源码片段:
deque() : map(), map_size(0), start(0), finish(0), first(0), last(0) {map.push_back(allocate_block());map_size = 1;
}
详解:
- 默认构造函数初始化一个空
deque
。 map
中插入一个新分配的内存块,map_size
设为 1。start
和finish
指向同一块,first
和last
为 0,表示当前无元素。
3. 尾部插入(push_back)
源码片段:
void push_back(const T& value) {if (last == block_size || finish == map_size - 1) {if (finish == map_size - 1) {reallocate_map(map_size * 2 + 1);}map[++finish] = allocate_block();last = 0;}std::allocator_traits<Alloc>::construct(alloc, map[finish] + last, value);++last;
}
详解:
- 检查当前块是否满(
last == block_size
)或映射表是否无空间(finish == map_size - 1
)。 - 如果映射表满,调用
reallocate_map
扩展容量。 - 分配新块并更新
finish
,last
重置为 0。 - 在尾块的
last
位置构造新元素,last
递增。
4. 头部插入(push_front)
源码片段:
void push_front(const T& value) {if (first == 0) {if (start == 0) {reallocate_map(map_size * 2 + 1);start = map_size / 2;finish += start;}map[--start] = allocate_block();first = block_size;}std::allocator_traits<Alloc>::construct(alloc, map[start] + (--first), value);
}
详解:
- 如果首块无空间(
first == 0
),检查映射表头部是否可用。 - 若不可用,扩展映射表并调整
start
和finish
。 - 分配新块,
start
递减,first
设为block_size
。 - 在首块的
first-1
位置构造元素。
5. 尾部删除(pop_back)
源码片段:
void pop_back() {if (last == 0) {if (finish > start) {deallocate_block(map[finish--]);last = block_size;}}if (last > 0) {std::allocator_traits<Alloc>::destroy(alloc, map[finish] + (--last));}
}
详解:
- 若尾块为空(
last == 0
)且有多块,释放尾块并更新finish
和last
。 - 若尾块有元素,销毁最后一个元素,
last
递减。
6. 头部删除(pop_front)
源码片段:
void pop_front() {if (first == block_size - 1) {if (start < finish) {deallocate_block(map[start++]);first = 0;}}if (first < block_size) {std::allocator_traits<Alloc>::destroy(alloc, map[start] + (first++));}
}
详解:
- 若首块将空(
first == block_size - 1
)且有多块,释放首块并更新start
和first
。 - 若首块有元素,销毁第一个元素,
first
递增。
7. 元素访问(operator[])
源码片段:
reference operator[](size_type n) {size_type index = first + n;size_type block_index = start + index / block_size;size_type block_offset = index % block_size;return map[block_index][block_offset];
}
详解:
- 计算全局索引
index
,然后确定块索引和块内偏移。 - 返回对应位置的元素引用,支持随机访问。
8. 大小计算(size)
源码片段:
size_type size() const {return (finish - start) * block_size + last - first;
}
详解:
- 计算中间完整块的元素数,加上首尾块的元素数,得出总大小。
9. 映射重新分配(reallocate_map)
源码片段:
void reallocate_map(size_type new_size) {map_type new_map(new_size, nullptr);std::copy(map.begin(), map.end(), new_map.begin());map.swap(new_map);map_size = new_size;
}
详解:
- 创建新映射表,复制原有块指针,交换并更新
map_size
。
10. 完整源码示例
#include <vector>
#include <memory>
#include <stdexcept>
#include <cassert>template <typename T, typename Alloc = std::allocator<T>>
class deque {
public:using value_type = T;using allocator_type = Alloc;using size_type = std::size_t;using reference = value_type&;using const_reference = const value_type&;using pointer = typename std::allocator_traits<Alloc>::pointer;private:static const size_type block_size = 16; // Size of each blockusing block_type = T*; // Block typeusing map_type = std::vector<block_type>; // Map type for block pointersmap_type map; // Stores block pointerssize_type map_size; // Size of the mapsize_type start; // Index of the first blocksize_type finish; // Index of the last blocksize_type first; // Offset of the first element in the start blocksize_type last; // Offset of the last element in the finish blockAlloc alloc; // Allocator// Allocate a new blockblock_type allocate_block() {return alloc.allocate(block_size);}// Deallocate a blockvoid deallocate_block(block_type block) {alloc.deallocate(block, block_size);}// Reallocate the map with a new sizevoid reallocate_map(size_type new_size) {map_type new_map(new_size, nullptr);size_type old_map_size = map.size();size_type offset = (new_size - old_map_size) / 2; // Center the existing blocksstd::copy(map.begin(), map.end(), new_map.begin() + offset);map.swap(new_map);start += offset;finish += offset;map_size = new_size;}// Destroy elements in a block within a rangevoid destroy_elements(block_type block, size_type begin, size_type end) {for (size_type i = begin; i < end; ++i) {std::allocator_traits<Alloc>::destroy(alloc, block + i);}}public:// Default constructordeque() : map(), map_size(0), start(0), finish(0), first(0), last(0) {map.push_back(allocate_block());map_size = 1;}// Destructor~deque() {for (size_type i = start; i <= finish; ++i) {if (i == start) {destroy_elements(map[i], first, block_size);} else if (i == finish) {destroy_elements(map[i], 0, last);} else {destroy_elements(map[i], 0, block_size);}deallocate_block(map[i]);}}// Push element to the backvoid push_back(const T& value) {if (last == block_size) {if (finish == map_size - 1) {reallocate_map(map_size * 2 + 1);}map[++finish] = allocate_block();last = 0;}std::allocator_traits<Alloc>::construct(alloc, map[finish] + last, value);++last;}// Push element to the frontvoid push_front(const T& value) {if (first == 0) {if (start == 0) {reallocate_map(map_size * 2 + 1);}map[--start] = allocate_block();first = block_size;}std::allocator_traits<Alloc>::construct(alloc, map[start] + (--first), value);}// Pop element from the backvoid pop_back() {if (last == 0) {if (finish > start) {deallocate_block(map[finish--]);last = block_size;}}if (last > 0) {std::allocator_traits<Alloc>::destroy(alloc, map[finish] + (--last));}}// Pop element from the frontvoid pop_front() {if (first == block_size - 1) {if (start < finish) {deallocate_block(map[start++]);first = 0;}}if (first < block_size) {std::allocator_traits<Alloc>::destroy(alloc, map[start] + (first++));}}// Access element by indexreference operator[](size_type n) {size_type total_index = first + n;size_type block_index = start + total_index / block_size;size_type block_offset = total_index % block_size;return map[block_index][block_offset];}// Return size of the dequesize_type size() const {return (finish - start) * block_size + last - first;}
};int main() {// 测试默认构造函数deque<int> d1;assert(d1.size() == 0);// 测试 push_backdeque<int> d2;d2.push_back(1);d2.push_back(2);assert(d2.size() == 2);assert(d2[0] == 1);assert(d2[1] == 2);// 测试 push_frontdeque<int> d3;d3.push_front(1);d3.push_front(2);assert(d3.size() == 2);assert(d3[0] == 2);assert(d3[1] == 1);// 测试 pop_backdeque<int> d4;d4.push_back(1);d4.push_back(2);d4.pop_back();assert(d4.size() == 1);assert(d4[0] == 1);// 测试 pop_frontdeque<int> d5;d5.push_front(1);d5.push_front(2);d5.pop_front();assert(d5.size() == 1);assert(d5[0] == 1);// 测试混合操作deque<int> d6;d6.push_back(1);d6.push_front(0);d6.push_back(2);d6.pop_front();assert(d6.size() == 2);assert(d6[0] == 1);assert(d6[1] == 2);return 0;
}
11. 总结
std::deque
通过分块存储和动态映射表,实现了高效的双端操作和随机访问,是 STL 中兼顾性能与灵活性的重要容器。