C/C++ API Reference
Loading...
Searching...
No Matches
dynamic_deque.h
1// Copyright 2025 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14#pragma once
15
16#include <cstddef>
17#include <cstdint>
18#include <initializer_list>
19#include <limits>
20#include <memory>
21#include <type_traits>
22#include <utility>
23
24#include "pw_allocator/allocator.h"
25#include "pw_assert/assert.h"
26#include "pw_containers/internal/generic_deque.h"
27#include "pw_numeric/saturating_arithmetic.h"
28
29namespace pw {
30
31namespace containers::internal {
32template <typename, typename>
33class GenericQueue;
34} // namespace containers::internal
35
37
40
70template <typename ValueType, typename SizeType = uint16_t>
72 : public containers::internal::
73 GenericDeque<DynamicDeque<ValueType, SizeType>, ValueType, SizeType> {
74 private:
75 using Base = containers::internal::
76 GenericDeque<DynamicDeque<ValueType, SizeType>, ValueType, SizeType>;
77
78 public:
79 using typename Base::const_iterator;
80 using typename Base::const_pointer;
81 using typename Base::const_reference;
82 using typename Base::difference_type;
83 using typename Base::iterator;
84 using typename Base::pointer;
85 using typename Base::reference;
86 using typename Base::size_type;
87 using typename Base::value_type;
88
90
95 constexpr DynamicDeque(Allocator& allocator) noexcept
96 : Base(0), allocator_(&allocator), buffer_(nullptr) {}
97
98 DynamicDeque(const DynamicDeque&) = delete;
99 DynamicDeque& operator=(const DynamicDeque&) = delete;
100
104 constexpr DynamicDeque(DynamicDeque&& other) noexcept
105 : Base(0), allocator_(other.allocator_), buffer_(other.buffer_) {
106 other.buffer_ = nullptr; // clear other's buffer_, but not its allocator_
107 Base::MoveAssignIndices(other);
108 }
109
110 DynamicDeque& operator=(DynamicDeque&& other) noexcept;
111
113
114 // Provide try_* versions of functions that return false if allocation fails.
115 using Base::try_assign;
116 using Base::try_emplace;
117 using Base::try_emplace_back;
118 using Base::try_emplace_front;
119 using Base::try_insert;
120 using Base::try_push_back;
121 using Base::try_push_front;
122 using Base::try_resize;
123
124 // The GenericDeque's input iterator insert implementation emplaces items one
125 // at a time, which is inefficient. For DynamicDeque, use a more efficient
126 // implementation that inserts all items into a temporary DynamicDeque first.
127 template <typename InputIt,
128 typename = containers::internal::EnableIfInputIterator<InputIt>>
129 iterator insert(const_iterator pos, InputIt first, InputIt last);
130
131 iterator insert(const_iterator pos, const value_type& value) {
132 return Base::insert(pos, value);
133 }
134
135 iterator insert(const_iterator pos, value_type&& value) {
136 return Base::insert(pos, std::move(value));
137 }
138
139 iterator insert(const_iterator pos,
140 size_type count,
141 const value_type& value) {
142 return Base::insert(pos, count, value);
143 }
144
145 iterator insert(const_iterator pos, std::initializer_list<value_type> ilist) {
146 return Base::insert(pos, ilist);
147 }
148
159 [[nodiscard]] bool try_reserve(size_type new_capacity);
160
162 void reserve(size_type new_capacity) { PW_ASSERT(try_reserve(new_capacity)); }
163
173 [[nodiscard]] bool try_reserve_exact(size_type new_capacity) {
174 return new_capacity <= Base::capacity() || IncreaseCapacity(new_capacity);
175 }
176
178 void reserve_exact(size_type new_capacity) {
179 PW_ASSERT(try_reserve_exact(new_capacity));
180 }
181
185
195 void reset() {
196 Base::clear();
197 DeallocateBuffer();
198 }
199
200 constexpr size_type max_size() const noexcept {
201 return std::numeric_limits<size_type>::max();
202 }
203
205 constexpr allocator_type& get_allocator() const { return *allocator_; }
206
208 void swap(DynamicDeque& other) noexcept {
209 Base::SwapIndices(other);
210 std::swap(allocator_, other.allocator_);
211 std::swap(buffer_, other.buffer_);
212 }
213
214 private:
215 friend Base;
216
217 template <typename, typename>
219
220 template <typename, typename>
221 friend class DynamicVector; // Allow direct access to data()
222
223 static constexpr bool kFixedCapacity = false; // uses dynamic allocation
224
225 // Hide full() since the capacity can grow.
226 using Base::full;
227
228 // Hide overwrite methods since capacity can grow.
233
234 pointer data() { return std::launder(reinterpret_cast<pointer>(buffer_)); }
235 const_pointer data() const {
236 return std::launder(reinterpret_cast<const_pointer>(buffer_));
237 }
238
239 [[nodiscard]] bool IncreaseCapacity(size_type new_capacity);
240
241 size_type GetNewCapacity(const size_type new_size) {
242 // For the initial allocation, allocate at least 4 words worth of items.
243 if (Base::capacity() == 0) {
244 return std::max(size_type{4 * sizeof(void*) / sizeof(value_type)},
245 new_size);
246 }
247 // Double the capacity. May introduce other allocation policies later.
248 return std::max(mul_sat(Base::capacity(), size_type{2}), new_size);
249 }
250
251 bool ReallocateBuffer(size_type new_capacity);
252
253 void DeallocateBuffer() {
254 allocator_->Deallocate(buffer_);
255 buffer_ = nullptr;
256 Base::HandleShrunkBuffer(0);
257 }
258
259 Allocator* allocator_;
260 std::byte* buffer_; // raw array for in-place construction and destruction
261};
262
263template <typename ValueType, typename SizeType>
264DynamicDeque<ValueType, SizeType>& DynamicDeque<ValueType, SizeType>::operator=(
265 DynamicDeque&& other) noexcept {
266 Base::DestroyAll();
267 allocator_->Deallocate(buffer_);
268
269 allocator_ = other.allocator_; // The other deque keeps its allocator
270 buffer_ = std::exchange(other.buffer_, nullptr);
271
272 Base::MoveAssignIndices(other);
273 return *this;
274}
275
276template <typename ValueType, typename SizeType>
277DynamicDeque<ValueType, SizeType>::~DynamicDeque() {
278 Base::DestroyAll();
279 allocator_->Deallocate(buffer_);
280}
281
282template <typename ValueType, typename SizeType>
284 return new_capacity <= Base::capacity() ||
285 IncreaseCapacity(GetNewCapacity(new_capacity)) ||
286 IncreaseCapacity(new_capacity);
287}
288
289template <typename ValueType, typename SizeType>
291 size_type new_capacity) {
292 // Try resizing the existing array. Only works if inserting at the end.
293 if (buffer_ != nullptr && Base::CanExtendBuffer() &&
294 allocator_->Resize(buffer_, new_capacity * sizeof(value_type))) {
295 Base::HandleExtendedBuffer(new_capacity);
296 return true;
297 }
298
299 // Allocate a new array and move items to it.
300 return ReallocateBuffer(new_capacity);
301}
302
303template <typename ValueType, typename SizeType>
305 if (Base::size() == Base::capacity()) {
306 return; // Nothing to do; deque is full or buffer_ is nullptr
307 }
308
309 if (Base::empty()) { // Empty deque, but a buffer_ is allocated; free it
310 DeallocateBuffer();
311 return;
312 }
313
314 // Attempt to shrink if buffer if possible, and reallocate it if needed.
315 //
316 // If there are unused slots at the start, could shift back and Resize()
317 // instead of calling ReallocateBuffer(), but may not be worth the complexity.
318 if (Base::CanShrinkBuffer() &&
319 allocator_->Resize(buffer_, Base::size() * sizeof(value_type))) {
320 Base::HandleShrunkBuffer(Base::size());
321 } else {
322 // TODO: b/498348047 - Consider dering + resize or simply giving up.
323 ReallocateBuffer(Base::size());
324 }
325}
326
327template <typename ValueType, typename SizeType>
329 size_type new_capacity) {
330 std::byte* new_buffer = static_cast<std::byte*>(
331 allocator_->Allocate(allocator::Layout::Of<value_type[]>(new_capacity)));
332 if (new_buffer == nullptr) {
333 return false;
334 }
335
336 pointer dest = std::launder(reinterpret_cast<pointer>(new_buffer));
337 auto [data_1, data_2] = Base::contiguous_data();
338
339 if constexpr (std::is_move_constructible_v<value_type>) {
340 dest = std::uninitialized_move(data_1.begin(), data_1.end(), dest);
341 std::uninitialized_move(data_2.begin(), data_2.end(), dest);
342 } else { // if it can't be moved, try copying
343 dest = std::uninitialized_copy(data_1.begin(), data_1.end(), dest);
344 std::uninitialized_copy(data_2.begin(), data_2.end(), dest);
345 }
346
347 std::destroy(data_1.begin(), data_1.end());
348 std::destroy(data_2.begin(), data_2.end());
349
350 allocator_->Deallocate(buffer_);
351 buffer_ = new_buffer;
352
353 Base::HandleNewBuffer(new_capacity);
354 return true;
355}
356
357template <typename ValueType, typename SizeType>
358template <typename InputIt, typename>
359typename DynamicDeque<ValueType, SizeType>::iterator
360DynamicDeque<ValueType, SizeType>::insert(const_iterator pos,
361 InputIt first,
362 InputIt last) {
363 // Can't safely check std::distance for InputIterator. Use a workaround.
364 if constexpr (std::is_same_v<std::input_iterator_tag,
365 typename std::iterator_traits<
366 InputIt>::iterator_category>) {
367 // Read into a temporary deque so the items can be counted. Then, move into
368 // this deque in one operation. This way, existing items are shifted once to
369 // their final positions, instead of shifting N times for repeated inserts.
370 DynamicDeque temp(*allocator_);
371 temp.assign(first, last);
372 return Base::insert(pos,
373 std::make_move_iterator(temp.data()),
374 std::make_move_iterator(temp.data() + temp.size()));
375 } else { // Use the efficient base implementation for forward iterators.
376 return Base::insert(pos, first, last);
377 }
378}
379
380} // namespace pw
Definition: allocator.h:42
Definition: dynamic_deque.h:73
Definition: dynamic_vector.h:63
Definition: generic_deque.h:195
constexpr size_type capacity() const noexcept
Returns the maximum number of elements in the deque.
Definition: generic_deque.h:72
Definition: generic_queue.h:25
void Deallocate(void *ptr)
Definition: deallocator.h:314
iterator insert(const_iterator pos, const value_type &value)
Definition: generic_deque.h:424
void push_back_overwrite(const value_type &value)
Definition: generic_deque.h:340
void reserve(size_type new_capacity)
Increases capacity() to at least new_capacity. Crashes on failure.
Definition: dynamic_deque.h:162
std::optional< iterator > try_insert(const_iterator pos, const value_type &value)
Definition: generic_deque.h:542
bool try_assign(size_type count, const value_type &value)
Definition: generic_deque.h:691
void emplace_front_overwrite(Args &&... args)
Definition: generic_deque.h:396
void reserve_exact(size_type new_capacity)
Increases capacity() to exactly new_capacity. Crashes on failure.
Definition: dynamic_deque.h:178
constexpr DynamicDeque(DynamicDeque &&other) noexcept
Definition: dynamic_deque.h:104
std::optional< iterator > try_emplace(const_iterator pos, Args &&... args)
void swap(DynamicDeque &other) noexcept
Swaps the contents of two deques. No allocations occur.
Definition: dynamic_deque.h:208
bool try_reserve(size_type new_capacity)
Definition: dynamic_deque.h:283
void shrink_to_fit()
Definition: dynamic_deque.h:304
constexpr allocator_type & get_allocator() const
Returns the deque's allocator.
Definition: dynamic_deque.h:205
void reset()
Clears the deque and deallocates its buffer.
Definition: dynamic_deque.h:195
bool try_reserve_exact(size_type new_capacity)
Definition: dynamic_deque.h:173
constexpr DynamicDeque(Allocator &allocator) noexcept
Definition: dynamic_deque.h:95
void emplace_back_overwrite(Args &&... args)
Definition: generic_deque.h:353
void push_front_overwrite(const value_type &value)
Definition: generic_deque.h:383
constexpr T mul_sat(T lhs, T rhs) noexcept
Definition: saturating_arithmetic.h:75
The Pigweed namespace.
Definition: alignment.h:27