libboxes
boxes is a set of specialised containers built on top of STL
linked_vector.hpp
Go to the documentation of this file.
1
12#ifndef __BOXES_LINKED_VECTOR_HPP__
13#define __BOXES_LINKED_VECTOR_HPP__
14
15#include "compiler.hpp"
16#include "ring_buffer.hpp"
17
18#include <list>
19#include <memory>
20#include <stdexcept>
21
22namespace boxes {
23
44template <typename T, std::size_t ChunkSizeV> class LinkedVector {
45public:
46 using value_type = T;
47 using reference = T &;
48 using const_reference = const T &;
49 using size_type = std::size_t;
50 using difference_type = std::ptrdiff_t;
51
52 static constexpr std::size_t ChunkSize = ChunkSizeV;
53
54 class iterator {
55 public:
56 using value_type = T;
57 using reference = T &;
58 using pointer = T *;
59 using difference_type = std::ptrdiff_t;
60 using iterator_category = std::random_access_iterator_tag;
61
62 iterator(LinkedVector<T, ChunkSizeV> *cnt, std::size_t pos)
63 : cnt{cnt}, pos{pos} {}
64
65 iterator &operator++() {
66 if (pos != cnt->size()) {
67 ++pos;
68 }
69 return *this;
70 }
71
72 iterator operator++(int) {
73 iterator tmp{*this};
74 ++(*this);
75 return tmp;
76 }
77
78 iterator &operator--() {
79 if (pos != 0) {
80 --pos;
81 }
82 return *this;
83 }
84
85 iterator operator--(int) {
86 iterator tmp{*this};
87 --(*this);
88 return tmp;
89 }
90
91 reference operator*() { return cnt->at(pos); }
92
93 pointer operator->() { return &cnt->at(pos); }
94
95 bool operator==(const iterator &other) const {
96 return cnt == other.cnt && pos == other.pos;
97 }
98
99 bool operator!=(const iterator &other) const { return !(*this == other); }
100
101 protected:
103 std::size_t pos;
104 };
105
112 LinkedVector() { initialise(); }
113
114 LinkedVector(const LinkedVector<T, ChunkSizeV> &other) = delete;
115
122 : chunks{std::move(other.chunks)},
123 discardedChunks{std::move(other.discardedChunks)},
124 frontChunk{other.frontChunk}, backChunk{other.backChunk} {
125 other.frontChunk = nullptr;
126 other.backChunk = nullptr;
127 }
128
130 operator=(const LinkedVector<T, ChunkSizeV> &other) = delete;
131
139 if (this != &other) {
140 LinkedVector<T, ChunkSizeV> tmp(std::move(other));
141 swap(tmp);
142 }
143 return *this;
144 }
145
154 std::swap(chunks, other.chunks);
155 std::swap(discardedChunks, other.discardedChunks);
156 std::swap(frontChunk, other.frontChunk);
157 std::swap(backChunk, other.backChunk);
158 }
159
173 template <typename Arg> void push_front(Arg &&value) {
174 expandFront();
175 frontChunk->push_front(std::forward<Arg>(value));
176 }
177
191 template <typename Arg> void push_back(Arg &&value) {
192 expandBack();
193 backChunk->push_back(std::forward<Arg>(value));
194 }
195
204 void pop_front() {
205 if (frontChunk == backChunk && frontChunk->empty()) {
206 return;
207 }
208
209 frontChunk->pop_front();
210 trim_front();
211 }
212
221 void pop_back() {
222 if (frontChunk == backChunk && backChunk->empty()) {
223 return;
224 }
225
226 backChunk->pop_back();
227 trim_back();
228 }
229
238 T &front() {
239 if (frontChunk->empty()) {
240 throw std::out_of_range("LinkedVector::front");
241 }
242
243 return frontChunk->front();
244 }
245
254 const T &front() const { return const_cast<LinkedVector *>(this)->front(); }
255
264 T &back() {
265 if (backChunk->empty()) {
266 throw std::out_of_range("LinkedVector::back");
267 }
268
269 return backChunk->back();
270 }
271
277 const T &back() const { return const_cast<LinkedVector *>(this)->back(); }
278
287 T &operator[](size_type pos) {
288 if (const auto &fs = frontChunk->size(); pos < fs) {
289 return frontChunk->operator[](pos);
290 } else {
291 pos -= fs;
292 auto chunkIndex = pos / ChunkSizeV + 1;
293 auto chunkOffset = pos % ChunkSizeV;
294
295 auto it = chunks.begin();
296 std::advance(it, chunkIndex);
297 return it->get()->operator[](chunkOffset);
298 }
299
300 boxes_unreachable();
301 }
302
311 const T &operator[](size_type pos) const {
312 return const_cast<LinkedVector *>(this)->operator[](pos);
313 }
314
324 T &at(size_type pos) {
325 if (pos >= size()) {
326 throw std::out_of_range("LinkedVector::at");
327 }
328 return (*this)[pos];
329 }
330
340 const T &at(size_type pos) const {
341 return const_cast<LinkedVector *>(this)->at(pos);
342 }
343
349 iterator begin() { return iterator(this, 0); }
350
358 iterator end() { return iterator(this, size()); }
359
365 iterator cbegin() const { return begin(); }
366
373 iterator cend() const { return end(); }
374
384 bool operator==(const LinkedVector<T, ChunkSizeV> &other) const {
385 if (size() != other.size()) {
386 return false;
387 }
388
389 for (auto it = chunks.cbegin(), otherIt = other.chunks.cbegin();
390 it != chunks.cend(); ++it, ++otherIt) {
391 if (**it != **otherIt) {
392 return false;
393 }
394 }
395
396 return true;
397 }
398
405 bool operator!=(const LinkedVector<T, ChunkSizeV> &other) const {
406 return !(*this == other);
407 }
408
414 std::size_t size() const BOXES_NOTHROW {
415 switch (chunks.size()) {
416 case 0:
417 return 0;
418 case 1:
419 return frontChunk->size();
420 case 2:
421 return frontChunk->size() + backChunk->size();
422 default:
423 return frontChunk->size() + backChunk->size() +
424 (chunks.size() - 2) * ChunkSizeV;
425 }
426
427 boxes_unreachable();
428 }
429
444 std::size_t capacity() const BOXES_NOTHROW {
445 return chunks.size() * ChunkSizeV;
446 }
447
451 void clear() {
452 chunks.clear();
453 discardedChunks.clear();
454 initialise();
455 }
456
462 bool empty() const BOXES_NOTHROW { return size() == 0; }
463
464protected:
465 using Chunk = RingBuffer<T, ChunkSizeV>;
466 std::list<std::unique_ptr<Chunk>> chunks;
467 std::list<std::unique_ptr<Chunk>> discardedChunks;
468 Chunk *frontChunk{nullptr};
469 Chunk *backChunk{nullptr};
470
471 void trim_front() {
472 if (frontChunk != backChunk && frontChunk->empty()) {
473 discardedChunks.push_back(std::move(chunks.front()));
474 chunks.pop_front();
475 frontChunk = chunks.front().get();
476 }
477 }
478
479 void trim_back() {
480 if (frontChunk != backChunk && backChunk->empty()) {
481 discardedChunks.push_back(std::move(chunks.back()));
482 chunks.pop_back();
483 backChunk = chunks.back().get();
484 }
485 }
486
487 void expandFront() {
488 if (frontChunk->full()) {
489 auto c = getChunk();
490 frontChunk = c.get();
491 chunks.push_front(std::move(c));
492 }
493 }
494
495 void expandBack() {
496 if (backChunk->full()) {
497 auto c = getChunk();
498 backChunk = c.get();
499 chunks.push_back(std::move(c));
500 }
501 }
502
503 std::unique_ptr<Chunk> getChunk() {
504 if (discardedChunks.empty()) {
505 return std::make_unique<Chunk>();
506 }
507
508 auto c = std::move(discardedChunks.front());
509 c->clear();
510 discardedChunks.pop_front();
511 return c;
512 }
513
514 void initialise() {
515 chunks.push_back(getChunk());
516 frontChunk = chunks.front().get();
517 backChunk = chunks.back().get();
518 }
519};
520
521} // namespace boxes
522
523#endif // __BOXES_LINKED_VECTOR_HPP__
Implements a vector-like container that uses a linked list of fixed-size chunks to store its elements...
const T & operator[](size_type pos) const
Returns a const reference to the element at the specified position.
bool operator!=(const LinkedVector< T, ChunkSizeV > &other) const
Returns true if the two containers are not equal.
T & front()
Returns a reference to the first element in the container.
iterator cbegin() const
Returns a const iterator to the first element of the container.
const T & front() const
Returns a const reference to the first element in the container.
LinkedVector(LinkedVector< T, ChunkSizeV > &&other)
Move constructor.
std::size_t size() const BOXES_NOTHROW
Returns the number of elements in the container.
std::size_t capacity() const BOXES_NOTHROW
Returns the maximum number of elements the container can hold without additional memory allocation.
T & back()
Returns a reference to the last element in the container.
bool operator==(const LinkedVector< T, ChunkSizeV > &other) const
Returns true if the two containers are equal.
void swap(LinkedVector< T, ChunkSizeV > &other)
Swaps the contents of this LinkedVector with another.
const T & back() const
Returns a const reference to the last element in the container.
void pop_front()
Removes the first element from the container.
void clear()
Removes all elements from the container.
LinkedVector< T, ChunkSizeV > & operator=(LinkedVector< T, ChunkSizeV > &&other)
Move assignment operator.
T & at(size_type pos)
Returns a reference to the element at the specified position.
iterator end()
Returns an iterator to the element following the last element of the container.
void pop_back()
Removes the last element from the container.
LinkedVector()
Constructs an empty container.
T & operator[](size_type pos)
Provides access to the element at the specified position.
iterator begin()
Returns an iterator to the first element of the container.
void push_front(Arg &&value)
Pushes a new element to the front of the container.
bool empty() const BOXES_NOTHROW
Returns true if the container is empty.
iterator cend() const
Returns a const iterator to the element following the last element.
void push_back(Arg &&value)
Pushes a new element to the back of the container.
const T & at(size_type pos) const
Returns a const reference to the element at the specified position.
Implements a fixed-size double ended queue.
Definition: ring_buffer.hpp:31
bool pop_front()
Removes the first element from the RingBuffer.
bool push_back(Arg &&value)
Inserts a new element at the end of the RingBuffer.
bool full() const BOXES_NOTHROW
Returns true if the RingBuffer is full.
bool pop_back()
Removes the last element from the RingBuffer.
T & front()
Returns the first element in the queue.
T & back()
Returns the last element in the queue.
size_type size() const BOXES_NOTHROW
Returns the number of elements in the RingBuffer.
bool empty() const BOXES_NOTHROW
Returns true if the RingBuffer is empty.
bool push_front(Arg &&value)
Inserts a new element at the front of the RingBuffer.
Implements a fixed-size double-ended queue.