![]() |
MySQL 9.7.0
Source Code Documentation
|
A (partial) implementation of std::deque allocating its blocks on a MEM_ROOT. More...
#include <mem_root_deque.h>
Classes | |
| struct | Block |
| class | Iterator |
Public Types | |
| using | size_type = size_t |
| Used to conform to STL algorithm demands. More... | |
| using | difference_type = ptrdiff_t |
| using | value_type = Element_type |
| using | pointer = Element_type * |
| using | reference = Element_type & |
| using | const_pointer = const Element_type * |
| using | const_reference = const Element_type & |
| using | iterator = Iterator< Element_type > |
| using | const_iterator = Iterator< const Element_type > |
| using | reverse_iterator = std::reverse_iterator< iterator > |
| using | reverse_const_iterator = std::reverse_iterator< const_iterator > |
Public Member Functions | |
| mem_root_deque (MEM_ROOT *mem_root) | |
| Constructor. Leaves the array in an empty, valid state. More... | |
| mem_root_deque (const mem_root_deque &other) | |
| mem_root_deque & | operator= (const mem_root_deque &other) |
| mem_root_deque (mem_root_deque &&other) | |
| mem_root_deque & | operator= (mem_root_deque &&other) |
| ~mem_root_deque () | |
| Element_type & | operator[] (size_t idx) const |
| bool | push_back (const Element_type &element) |
| Adds the given element to the end of the deque. More... | |
| bool | push_back (Element_type &&element) |
| Adds the given element to the end of the deque. More... | |
| bool | push_front (const Element_type &element) |
| Adds the given element to the beginning of the deque. More... | |
| bool | push_front (Element_type &&element) |
| Adds the given element to the end of the deque. More... | |
| void | pop_back () |
| Removes the last element from the deque. More... | |
| void | pop_front () |
| Removes the first element from the deque. More... | |
| Element_type & | front () |
| Returns the first element in the deque. More... | |
| const Element_type & | front () const |
| Element_type & | back () |
| Returns the last element in the deque. More... | |
| const Element_type & | back () const |
| void | clear () |
| Removes all elements from the deque. More... | |
| size_t | block_slots () const |
| size_t | first_block_idx () const |
| uint8_t | back_growth_exp () const |
| uint8_t | front_growth_exp () const |
| size_t | capacity () const |
| iterator | begin () |
| iterator | end () |
| reverse_iterator | rbegin () |
| reverse_iterator | rend () |
| const_iterator | cbegin () |
| const_iterator | cend () |
| const_iterator | begin () const |
| const_iterator | end () const |
| reverse_const_iterator | crbegin () const |
| reverse_const_iterator | crend () const |
| reverse_const_iterator | rbegin () const |
| reverse_const_iterator | rend () const |
| size_t | size () const |
| bool | empty () const |
| iterator | erase (const_iterator first, const_iterator last) |
| Erase all the elements in the specified range. More... | |
| iterator | erase (const_iterator position) |
| Removes a single element from the array. More... | |
| iterator | insert (const_iterator pos, const Element_type &value) |
| Insert an element at a given position. More... | |
| iterator | insert (const_iterator pos, Element_type &&value) |
| Insert an element at a given position. More... | |
| template<class ForwardIt > | |
| iterator | insert (const_iterator pos, ForwardIt first, ForwardIt last) |
Private Member Functions | |
| void | invalidate_iterators () |
| bool | add_initial_block () |
| Adds the first block of elements. More... | |
| bool | add_block_back () |
| bool | add_block_front () |
| size_t | num_blocks () const |
| Element_type & | get (size_t physical_idx) const |
| Gets a reference to the memory used to store an element with the given physical index, starting from zero. More... | |
| size_t | generation () const |
Private Attributes | |
| Block * | m_blocks = nullptr |
| Pointer to the first block. More... | |
| size_t | m_begin_idx = 0 |
| Physical index to the first valid element. More... | |
| size_t | m_end_idx = 0 |
| Physical index one past the last valid element. More... | |
| size_t | m_capacity = 0 |
| Number of blocks, multiplied by block_elements. More... | |
| size_t | m_block_slots = 0 |
| Number of Block slots allocated in m_blocks array. More... | |
| size_t | m_first_block_idx = 0 |
| Index in m_blocks where the first initialized block is stored. More... | |
| uint8_t | m_back_growth_exp = 0 |
| Growth exponent for back expansion. More... | |
| uint8_t | m_front_growth_exp = 0 |
| Growth exponent for front expansion. More... | |
| MEM_ROOT * | m_root |
| Pointer to the MEM_ROOT that we store our blocks and elements on. More... | |
| size_t | m_generation = 0 |
| Incremented each time we make an operation that would invalidate iterators. More... | |
Static Private Attributes | |
| static constexpr size_t | block_elements = FindElementsPerBlock<Element_type>() |
| Number of elements in each block. More... | |
A (partial) implementation of std::deque allocating its blocks on a MEM_ROOT.
This class works pretty much like an std::deque with a Mem_root_allocator, and used to be a forwarder to it. However, libstdc++ has a very complicated implementation of std::deque, leading to code blowup (e.g., operator[] is 23 instructions on x86-64, including two branches), and we cannot easily use libc++ on all platforms. This version is instead:
It gives mostly the same guarantees as std::deque; elements can be inserted efficiently on both front and back [1]. {push,pop}_{front,back} guarantees reference stability except for to removed elements (obviously), and invalidates iterators. (Iterators are forcefully invalidated using asserts.) erase() does the same. Note that unlike std::deque, insert() at begin() will invalidate references. Some functionality, like several constructors, resize(), shrink_to_fit(), swap(), etc. is missing.
The implementation is the same as classic std::deque: Elements are held in blocks of about 1 kB each. Once an element is in a block, it never moves (giving the aforementioned pointer stability). The blocks are held in an array, which can be reallocated when more blocks are needed. The implementation keeps track of the used items in terms of “physical indexes”; element 0 starts at the first byte of the first block, element 1 starts immediately after 0, and so on. So you can have something like (assuming very small blocks of only 4 elements each for the sake of drawing):
block 0 block 1 block 2
↓ ↓ ↓
[x x 2 3] [4 5 6 7] [8 9 x x]
↑ ↑
begin_idx = 2 end_idx = 10
end_idx counts as is customary one-past-the-end, so in this case, the elements [2,9) would be valid, and e.g. (*this)[4] would give physical index 6, which points to the third element (index 2) in the middle block (block 1). Inserting a new element at the front is as easy as putting it in physical index 1 and adjusting begin_idx to the left. This means a lookup by index requires some shifting, masking and a double indirect load.
Iterators keep track of which deque they belong to, and what physical index they point to. (So lookup by iterator also requires a double indirect load, but the alternative would be caching the block pointer and having an extra branch when advancing the iterator.) Inserting a new block at the beginning would move around all the physical indexes (which is why iterators get invalidated; we could probably get around this by having an “offset to first block”, but it's likely not worth it.)
[1] The blocks array uses exponential growth (doubling), so insertions are amortized O(1). The blocks array is reallocated rarely (O(log n) times for n insertions).
To support exponential growth, we track additional state beyond the blocks array pointer:
m_block_slots - Total slots in the blocks array (may exceed num_blocks). m_first_block_idx - Index where used blocks start (enables front spare). m_back_growth_exp - Growth exponent for back (next growth adds 2^exp slots). m_front_growth_exp - Growth exponent for front (next growth adds 2^exp slots).
The growth exponents track history, not current state. After growing back 3 times, we've created 1+2+4=7 spare slots. But some may be consumed, so we can't derive the exponent from current spare - we must track it explicitly.
Example: Growth triggered by push_back (back-only expansion)
Initial state (3 used blocks, no spare): m_blocks: [B0][B1][B2] m_first_block_idx: 0 m_block_slots: 3 m_back_growth_exp: 0
After 1st back growth (adds 2^0 = 1 spare slot at back): m_blocks: [B0][B1][B2][ ] ↑ spare m_block_slots: 4 m_back_growth_exp: 1
After 2nd back growth (adds 2^1 = 2 spare slots at back): m_blocks: [B0][B1][B2][B3][ ][ ] ↑ spare m_block_slots: 6 m_back_growth_exp: 2
Example: Growth triggered by push_front (front-only expansion)
Initial state (3 used blocks, no spare at front): m_blocks: [B0][B1][B2] m_first_block_idx: 0 m_front_growth_exp: 0
After 1st front growth (adds 2^0 = 1 spare slot at front): m_blocks: [ ][B0][B1][B2] ↑ spare m_first_block_idx: 1 (new block goes here, then decrements to 0) m_block_slots: 4 m_front_growth_exp: 1
After 2nd front growth (adds 2^1 = 2 spare slots at front): m_blocks: [ ][ ][ ][B0][B1][B2] ↑ spare m_first_block_idx: 3 (new block goes at index 2) m_block_slots: 6 m_front_growth_exp: 2
Each direction grows independently. A deque with many push_back calls will have large m_back_growth_exp but m_front_growth_exp stays 0 until push_front triggers growth.
| using mem_root_deque< Element_type >::const_iterator = Iterator<const Element_type> |
| using mem_root_deque< Element_type >::const_pointer = const Element_type * |
| using mem_root_deque< Element_type >::const_reference = const Element_type & |
| using mem_root_deque< Element_type >::difference_type = ptrdiff_t |
| using mem_root_deque< Element_type >::iterator = Iterator<Element_type> |
| using mem_root_deque< Element_type >::pointer = Element_type * |
| using mem_root_deque< Element_type >::reference = Element_type & |
| using mem_root_deque< Element_type >::reverse_const_iterator = std::reverse_iterator<const_iterator> |
| using mem_root_deque< Element_type >::reverse_iterator = std::reverse_iterator<iterator> |
| using mem_root_deque< Element_type >::size_type = size_t |
Used to conform to STL algorithm demands.
| using mem_root_deque< Element_type >::value_type = Element_type |
|
inlineexplicit |
Constructor. Leaves the array in an empty, valid state.
|
inline |
|
inline |
|
inline |
|
private |
|
private |
|
inlineprivate |
Adds the first block of elements.
|
inline |
Returns the last element in the deque.
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
Removes all elements from the deque.
Destructors are called, but since the elements themselves are allocated on the MEM_ROOT, their memory cannot be freed.
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
Erase all the elements in the specified range.
| first | iterator that points to the first element to remove |
| last | iterator that points to the element after the last one to remove |
|
inline |
Removes a single element from the array.
| position | iterator that points to the element to remove |
|
inline |
|
inline |
Returns the first element in the deque.
|
inline |
|
inline |
|
inlineprivate |
|
inlineprivate |
Gets a reference to the memory used to store an element with the given physical index, starting from zero.
Note that block_elements is always a power of two, so the division and modulus operations are cheap.
|
inline |
Insert an element at a given position.
The element is a copy of the given one.
| pos | the new element is inserted before the element at this position |
| value | the value of the new element |
|
inline |
Insert an element at a given position.
The element is moved into place.
| pos | the new element is inserted before the element at this position |
| value | the value of the new element |
|
inline |
|
inlineprivate |
|
inlineprivate |
|
inline |
|
inline |
|
inline |
|
inline |
Removes the last element from the deque.
|
inline |
Removes the first element from the deque.
|
inline |
Adds the given element to the end of the deque.
The element is a copy of the given one.
|
inline |
Adds the given element to the end of the deque.
The element is moved into place.
|
inline |
Adds the given element to the beginning of the deque.
The element is a copy of the given one.
|
inline |
Adds the given element to the end of the deque.
The element is moved into place.
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
|
staticconstexprprivate |
Number of elements in each block.
|
private |
Growth exponent for back expansion.
When push_back needs to grow, we add 2^m_back_growth_exp spare slots at the back, then increment. This gives independent exponential growth: 1, 2, 4, 8, 16...
|
private |
Physical index to the first valid element.
|
private |
Number of Block slots allocated in m_blocks array.
This may be larger than num_blocks() to allow for exponential growth without reallocating on every new block.
|
private |
Pointer to the first block.
Can be nullptr if there are no elements (this makes the constructor cheaper). Stored on the MEM_ROOT, and needs no destruction, so just a raw pointer.
|
private |
Number of blocks, multiplied by block_elements.
(Storing this instead of the number of blocks itself makes testing in push_back cheaper.)
|
private |
Physical index one past the last valid element.
If begin == end, the array is empty (and then it doesn't matter what the values are).
|
private |
Index in m_blocks where the first initialized block is stored.
Blocks are stored contiguously from m_first_block_idx to m_first_block_idx + num_blocks() - 1. Spare slots before this index can be used for push_front without reallocating.
|
private |
Growth exponent for front expansion.
When push_front needs to grow, we add 2^m_front_growth_exp spare slots at the front, then increment.
|
private |
Incremented each time we make an operation that would invalidate iterators.
Asserts use this value in debug mode to be able to verify that they have not been invalidated. (In optimized mode, using an invalidated iterator incurs undefined behavior.)
|
private |
Pointer to the MEM_ROOT that we store our blocks and elements on.