|
| 1 | +// Copyright (c) The Bitcoin Core developers |
| 2 | +// Distributed under the MIT software license, see the accompanying |
| 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 4 | + |
| 5 | +#ifndef BITCOIN_UTIL_VECDEQUE_H |
| 6 | +#define BITCOIN_UTIL_VECDEQUE_H |
| 7 | + |
| 8 | +#include <util/check.h> |
| 9 | + |
| 10 | +#include <cstring> |
| 11 | +#include <memory> |
| 12 | + |
| 13 | +/** Data structure largely mimicking std::deque, but using single preallocated ring buffer. |
| 14 | + * |
| 15 | + * - More efficient and better memory locality than std::deque. |
| 16 | + * - Most operations ({push_,pop_,emplace_,}{front,back}(), operator[], ...) are O(1), |
| 17 | + * unless reallocation is needed (in which case they are O(n)). |
| 18 | + * - Supports reserve(), capacity(), shrink_to_fit() like vectors. |
| 19 | + * - No iterator support. |
| 20 | + * - Data is not stored in a single contiguous block, so no data(). |
| 21 | + */ |
| 22 | +template<typename T> |
| 23 | +class VecDeque |
| 24 | +{ |
| 25 | + /** Pointer to allocated memory. Can contain constructed and uninitialized T objects. */ |
| 26 | + T* m_buffer{nullptr}; |
| 27 | + /** m_buffer + m_offset points to first object in queue. m_offset = 0 if m_capacity is 0; |
| 28 | + * otherwise 0 <= m_offset < m_capacity. */ |
| 29 | + size_t m_offset{0}; |
| 30 | + /** Number of objects in the container. 0 <= m_size <= m_capacity. */ |
| 31 | + size_t m_size{0}; |
| 32 | + /** The size of m_buffer, expressed as a multiple of the size of T. */ |
| 33 | + size_t m_capacity{0}; |
| 34 | + |
| 35 | + /** Returns the number of populated objects between m_offset and the end of the buffer. */ |
| 36 | + size_t FirstPart() const noexcept { return std::min(m_capacity - m_offset, m_size); } |
| 37 | + |
| 38 | + void Reallocate(size_t capacity) |
| 39 | + { |
| 40 | + Assume(capacity >= m_size); |
| 41 | + Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity); |
| 42 | + // Allocate new buffer. |
| 43 | + T* new_buffer = capacity ? std::allocator<T>().allocate(capacity) : nullptr; |
| 44 | + if (capacity) { |
| 45 | + if constexpr (std::is_trivially_copyable_v<T>) { |
| 46 | + // When T is trivially copyable, just copy the data over from old to new buffer. |
| 47 | + size_t first_part = FirstPart(); |
| 48 | + if (first_part != 0) { |
| 49 | + std::memcpy(new_buffer, m_buffer + m_offset, first_part * sizeof(T)); |
| 50 | + } |
| 51 | + if (first_part != m_size) { |
| 52 | + std::memcpy(new_buffer + first_part, m_buffer, (m_size - first_part) * sizeof(T)); |
| 53 | + } |
| 54 | + } else { |
| 55 | + // Otherwise move-construct in place in the new buffer, and destroy old buffer objects. |
| 56 | + size_t old_pos = m_offset; |
| 57 | + for (size_t new_pos = 0; new_pos < m_size; ++new_pos) { |
| 58 | + std::construct_at(new_buffer + new_pos, std::move(*(m_buffer + old_pos))); |
| 59 | + std::destroy_at(m_buffer + old_pos); |
| 60 | + ++old_pos; |
| 61 | + if (old_pos == m_capacity) old_pos = 0; |
| 62 | + } |
| 63 | + } |
| 64 | + } |
| 65 | + // Deallocate old buffer and update housekeeping. |
| 66 | + std::allocator<T>().deallocate(m_buffer, m_capacity); |
| 67 | + m_buffer = new_buffer; |
| 68 | + m_offset = 0; |
| 69 | + m_capacity = capacity; |
| 70 | + Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity); |
| 71 | + } |
| 72 | + |
| 73 | + /** What index in the buffer does logical entry number pos have? */ |
| 74 | + size_t BufferIndex(size_t pos) const noexcept |
| 75 | + { |
| 76 | + Assume(pos < m_capacity); |
| 77 | + // The expression below is used instead of the more obvious (pos + m_offset >= m_capacity), |
| 78 | + // because the addition there could in theory overflow with very large deques. |
| 79 | + if (pos >= m_capacity - m_offset) { |
| 80 | + return (m_offset + pos) - m_capacity; |
| 81 | + } else { |
| 82 | + return m_offset + pos; |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + /** Specialization of resize() that can only shrink. Separate so that clear() can call it |
| 87 | + * without requiring a default T constructor. */ |
| 88 | + void ResizeDown(size_t size) noexcept |
| 89 | + { |
| 90 | + Assume(size <= m_size); |
| 91 | + if constexpr (std::is_trivially_destructible_v<T>) { |
| 92 | + // If T is trivially destructible, we do not need to do anything but update the |
| 93 | + // housekeeping record. Default constructor or zero-filling will be used when |
| 94 | + // the space is reused. |
| 95 | + m_size = size; |
| 96 | + } else { |
| 97 | + // If not, we need to invoke the destructor for every element separately. |
| 98 | + while (m_size > size) { |
| 99 | + std::destroy_at(m_buffer + BufferIndex(m_size - 1)); |
| 100 | + --m_size; |
| 101 | + } |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | +public: |
| 106 | + VecDeque() noexcept = default; |
| 107 | + |
| 108 | + /** Resize the deque to be exactly size size (adding default-constructed elements if needed). */ |
| 109 | + void resize(size_t size) |
| 110 | + { |
| 111 | + if (size < m_size) { |
| 112 | + // Delegate to ResizeDown when shrinking. |
| 113 | + ResizeDown(size); |
| 114 | + } else if (size > m_size) { |
| 115 | + // When growing, first see if we need to allocate more space. |
| 116 | + if (size > m_capacity) Reallocate(size); |
| 117 | + while (m_size < size) { |
| 118 | + std::construct_at(m_buffer + BufferIndex(m_size)); |
| 119 | + ++m_size; |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + /** Resize the deque to be size 0. The capacity will remain unchanged. */ |
| 125 | + void clear() noexcept { ResizeDown(0); } |
| 126 | + |
| 127 | + /** Destroy a deque. */ |
| 128 | + ~VecDeque() |
| 129 | + { |
| 130 | + clear(); |
| 131 | + Reallocate(0); |
| 132 | + } |
| 133 | + |
| 134 | + /** Copy-assign a deque. */ |
| 135 | + VecDeque& operator=(const VecDeque& other) |
| 136 | + { |
| 137 | + if (&other == this) [[unlikely]] return *this; |
| 138 | + clear(); |
| 139 | + Reallocate(other.m_size); |
| 140 | + if constexpr (std::is_trivially_copyable_v<T>) { |
| 141 | + size_t first_part = other.FirstPart(); |
| 142 | + Assume(first_part > 0 || m_size == 0); |
| 143 | + if (first_part != 0) { |
| 144 | + std::memcpy(m_buffer, other.m_buffer + other.m_offset, first_part * sizeof(T)); |
| 145 | + } |
| 146 | + if (first_part != other.m_size) { |
| 147 | + std::memcpy(m_buffer + first_part, other.m_buffer, (other.m_size - first_part) * sizeof(T)); |
| 148 | + } |
| 149 | + m_size = other.m_size; |
| 150 | + } else { |
| 151 | + while (m_size < other.m_size) { |
| 152 | + std::construct_at(m_buffer + BufferIndex(m_size), other[m_size]); |
| 153 | + ++m_size; |
| 154 | + } |
| 155 | + } |
| 156 | + return *this; |
| 157 | + } |
| 158 | + |
| 159 | + /** Swap two deques. */ |
| 160 | + void swap(VecDeque& other) noexcept |
| 161 | + { |
| 162 | + std::swap(m_buffer, other.m_buffer); |
| 163 | + std::swap(m_offset, other.m_offset); |
| 164 | + std::swap(m_size, other.m_size); |
| 165 | + std::swap(m_capacity, other.m_capacity); |
| 166 | + } |
| 167 | + |
| 168 | + /** Non-member version of swap. */ |
| 169 | + friend void swap(VecDeque& a, VecDeque& b) noexcept { a.swap(b); } |
| 170 | + |
| 171 | + /** Move-assign a deque. */ |
| 172 | + VecDeque& operator=(VecDeque&& other) noexcept |
| 173 | + { |
| 174 | + swap(other); |
| 175 | + return *this; |
| 176 | + } |
| 177 | + |
| 178 | + /** Copy-construct a deque. */ |
| 179 | + VecDeque(const VecDeque& other) { *this = other; } |
| 180 | + /** Move-construct a deque. */ |
| 181 | + VecDeque(VecDeque&& other) noexcept { swap(other); } |
| 182 | + |
| 183 | + /** Equality comparison between two deques (only compares size+contents, not capacity). */ |
| 184 | + bool friend operator==(const VecDeque& a, const VecDeque& b) |
| 185 | + { |
| 186 | + if (a.m_size != b.m_size) return false; |
| 187 | + for (size_t i = 0; i < a.m_size; ++i) { |
| 188 | + if (a[i] != b[i]) return false; |
| 189 | + } |
| 190 | + return true; |
| 191 | + } |
| 192 | + |
| 193 | + /** Comparison between two deques, implementing lexicographic ordering on the contents. */ |
| 194 | + std::strong_ordering friend operator<=>(const VecDeque& a, const VecDeque& b) |
| 195 | + { |
| 196 | + size_t pos_a{0}, pos_b{0}; |
| 197 | + while (pos_a < a.m_size && pos_b < b.m_size) { |
| 198 | + auto cmp = a[pos_a++] <=> b[pos_b++]; |
| 199 | + if (cmp != 0) return cmp; |
| 200 | + } |
| 201 | + return a.m_size <=> b.m_size; |
| 202 | + } |
| 203 | + |
| 204 | + /** Increase the capacity to capacity. Capacity will not shrink. */ |
| 205 | + void reserve(size_t capacity) |
| 206 | + { |
| 207 | + if (capacity > m_capacity) Reallocate(capacity); |
| 208 | + } |
| 209 | + |
| 210 | + /** Make the capacity equal to the size. The contents does not change. */ |
| 211 | + void shrink_to_fit() |
| 212 | + { |
| 213 | + if (m_capacity > m_size) Reallocate(m_size); |
| 214 | + } |
| 215 | + |
| 216 | + /** Construct a new element at the end of the deque. */ |
| 217 | + template<typename... Args> |
| 218 | + void emplace_back(Args&&... args) |
| 219 | + { |
| 220 | + if (m_size == m_capacity) Reallocate((m_size + 1) * 2); |
| 221 | + std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...); |
| 222 | + ++m_size; |
| 223 | + } |
| 224 | + |
| 225 | + /** Move-construct a new element at the end of the deque. */ |
| 226 | + void push_back(T&& elem) { emplace_back(std::move(elem)); } |
| 227 | + |
| 228 | + /** Copy-construct a new element at the end of the deque. */ |
| 229 | + void push_back(const T& elem) { emplace_back(elem); } |
| 230 | + |
| 231 | + /** Construct a new element at the beginning of the deque. */ |
| 232 | + template<typename... Args> |
| 233 | + void emplace_front(Args&&... args) |
| 234 | + { |
| 235 | + if (m_size == m_capacity) Reallocate((m_size + 1) * 2); |
| 236 | + std::construct_at(m_buffer + BufferIndex(m_capacity - 1), std::forward<Args>(args)...); |
| 237 | + if (m_offset == 0) m_offset = m_capacity; |
| 238 | + --m_offset; |
| 239 | + ++m_size; |
| 240 | + } |
| 241 | + |
| 242 | + /** Copy-construct a new element at the beginning of the deque. */ |
| 243 | + void push_front(const T& elem) { emplace_front(elem); } |
| 244 | + |
| 245 | + /** Move-construct a new element at the beginning of the deque. */ |
| 246 | + void push_front(T&& elem) { emplace_front(std::move(elem)); } |
| 247 | + |
| 248 | + /** Remove the first element of the deque. Requires !empty(). */ |
| 249 | + void pop_front() |
| 250 | + { |
| 251 | + Assume(m_size); |
| 252 | + std::destroy_at(m_buffer + m_offset); |
| 253 | + --m_size; |
| 254 | + ++m_offset; |
| 255 | + if (m_offset == m_capacity) m_offset = 0; |
| 256 | + } |
| 257 | + |
| 258 | + /** Remove the last element of the deque. Requires !empty(). */ |
| 259 | + void pop_back() |
| 260 | + { |
| 261 | + Assume(m_size); |
| 262 | + std::destroy_at(m_buffer + BufferIndex(m_size - 1)); |
| 263 | + --m_size; |
| 264 | + } |
| 265 | + |
| 266 | + /** Get a mutable reference to the first element of the deque. Requires !empty(). */ |
| 267 | + T& front() noexcept |
| 268 | + { |
| 269 | + Assume(m_size); |
| 270 | + return m_buffer[m_offset]; |
| 271 | + } |
| 272 | + |
| 273 | + /** Get a const reference to the first element of the deque. Requires !empty(). */ |
| 274 | + const T& front() const noexcept |
| 275 | + { |
| 276 | + Assume(m_size); |
| 277 | + return m_buffer[m_offset]; |
| 278 | + } |
| 279 | + |
| 280 | + /** Get a mutable reference to the last element of the deque. Requires !empty(). */ |
| 281 | + T& back() noexcept |
| 282 | + { |
| 283 | + Assume(m_size); |
| 284 | + return m_buffer[BufferIndex(m_size - 1)]; |
| 285 | + } |
| 286 | + |
| 287 | + /** Get a const reference to the last element of the deque. Requires !empty(). */ |
| 288 | + const T& back() const noexcept |
| 289 | + { |
| 290 | + Assume(m_size); |
| 291 | + return m_buffer[BufferIndex(m_size - 1)]; |
| 292 | + } |
| 293 | + |
| 294 | + /** Get a mutable reference to the element in the deque at the given index. Requires idx < size(). */ |
| 295 | + T& operator[](size_t idx) noexcept |
| 296 | + { |
| 297 | + Assume(idx < m_size); |
| 298 | + return m_buffer[BufferIndex(idx)]; |
| 299 | + } |
| 300 | + |
| 301 | + /** Get a const reference to the element in the deque at the given index. Requires idx < size(). */ |
| 302 | + const T& operator[](size_t idx) const noexcept |
| 303 | + { |
| 304 | + Assume(idx < m_size); |
| 305 | + return m_buffer[BufferIndex(idx)]; |
| 306 | + } |
| 307 | + |
| 308 | + /** Test whether the contents of this deque is empty. */ |
| 309 | + bool empty() const noexcept { return m_size == 0; } |
| 310 | + /** Get the number of elements in this deque. */ |
| 311 | + size_t size() const noexcept { return m_size; } |
| 312 | + /** Get the capacity of this deque (maximum size it can have without reallocating). */ |
| 313 | + size_t capacity() const noexcept { return m_capacity; } |
| 314 | +}; |
| 315 | + |
| 316 | +#endif // BITCOIN_UTIL_VECDEQUE_H |
0 commit comments