libbmb
Modern implementation of STL
Loading...
Searching...
No Matches
vector.h
Go to the documentation of this file.
1#pragma once
8#include <cstddef>
9#include <initializer_list>
10#include <stdexcept>
11
12#include "algo/algo_base.h"
14#include "utils/allocator.h"
15#include "utils/iterators.h"
16#include "utils/move.h"
17#include "utils/type_traits.h"
18
19namespace bmb {
20
34template <typename T, typename Allocator = PrimitiveAllocator>
35// for now block const types. see c++ std::launder and raw operator delete call on const type.
37class Vector {
38 // TODO: add template constraints
39 // TODO: add 'insert' support
40 // TODO: add reverse_iterator
41
43
44 // Used to mark whether to construct new element.
45 enum class PushDecide : char {
46 kDoPush,
47 kNoPush
48 };
49
50public:
51 using value_type = T;
55 using const_pointer = const value_type*;
60
62
63 explicit Vector(const Allocator& alloc)
64 : alloc_(alloc) {}
65
80 explicit Vector(size_t count, const value_type& value = value_type(),
81 const Allocator& alloc = Allocator())
82 : alloc_(alloc) {
83 // Possible problem: there is allocation and
84 // construction of the same number of elements, thus,
85 // in next push there will be reallocation.
86 // Possible solution: reserve more memory(e.g. 2*count)
87
89
90 try {
91 // Use size_ field for proper clear() usage in case of exception
92 for (; size_ < capacity_; ++size_) {
93 AllocTraits::construct(alloc_, arr_ + size_, value);
94 }
95 } catch (...) {
96 // Don't forget destroy all constructed elements
97 clear();
98 throw;
99 }
100 }
101
115 template <InputIterator Iter>
117 const Allocator& alloc = Allocator())
118 : alloc_(alloc) {
119 rangeInit(first, last);
120 }
121
134 Vector(std::initializer_list<value_type> init_list,
135 const Allocator& alloc = Allocator())
136 : alloc_(alloc) {
137 rangeInit(init_list.begin(), init_list.end());
138 }
139
148 : alloc_(other.alloc_) {
149 rangeInit(other.begin(), other.end());
150 }
151
152 Vector(Vector&& other) noexcept(noexcept(Allocator(move(other.alloc_))))
153 : arr_(other.arr_)
154 , size_(other.size_)
155 , capacity_(other.capacity_)
156 , alloc_(move(other.alloc_)) {
157 // NOTE: Another possible implementation: default construct + swap(*this, other).
158 // However it has a downside: Allocator must be default constructible,
159 // which either inefficent or inconvenient.
160 other.arr_ = nullptr;
161 other.size_ = 0;
162 other.capacity_ = 0;
163 }
164
174 swap(*this, other);
175 return *this;
176 }
177
178 ~Vector() { clear(); }
179
191 friend void swap(Vector& a, Vector& b) {
192 // Firstly try to swap allocators to provide strong exception safety.
193 swap(a.alloc_, b.alloc_);
194
195 swap(a.arr_, b.arr_);
196 swap(a.size_, b.size_);
197 swap(a.capacity_, b.capacity_);
198 }
199
205 bool isEmpty() const noexcept { return size_ == 0; }
206
223 void reserve(size_t new_capacity) {
224 pushingRealloc(new_capacity, PushDecide::kNoPush);
225 }
226
228 void pushBack(value_type value) {
229 emplaceBack(move(value));
230 }
231
254 template <typename... Args>
256 // There are several reasons why we must differentiate
257 // realloc + push and simple push:
258 // 1) Major reason: case v.emplaceBack(v[5]); for more
259 // details see `pushingRealloc` definition. Order of operations does matter.
260 // 2) Minor reason: pointer to the raw array(obtained from v.getRawData())
261 // might point to the previous storage while we allocated new one.
262 // 3) Minor reason: follwing from the standard definition of the
263 // strong exception guarantee.
264
265 constexpr size_t grow_coeff = 2;
266 constexpr size_t first_cap = 1;
267 if (size_ == capacity_) {
268 size_t new_cap = capacity_ > 0 ? grow_coeff * capacity_
269 : first_cap;
270
271 pushingRealloc(new_cap, PushDecide::kDoPush,
272 forward<Args>(args)...);
273 } else {
274 AllocTraits::construct(alloc_, arr_ + size_,
275 forward<Args>(args)...);
276 ++size_;
277 }
278
279 return back();
280 }
281
291 AllocTraits::destroy(alloc_, arr_ + size_ - 1);
292 --size_;
293 }
294
303 if (size_ == 0)
304 throw std::logic_error(
305 "Try to remove the last "
306 "element of the empty Vector");
307 popBack();
308 }
309
319 for (size_t i = 0; i < size_; ++i) {
320 AllocTraits::destroy(alloc_, arr_ + i);
321 }
322 AllocTraits::deallocate(alloc_, arr_, capacity_);
323
324 arr_ = nullptr;
325 size_ = 0;
326 capacity_ = 0;
327 }
328
332 size_t size() const noexcept { return size_; }
333
337 size_t capacity() const noexcept { return capacity_; }
338
342 pointer getRawData() noexcept { return arr_; }
343
348
353 reference operator[](size_t n) noexcept { return arr_[n]; }
354
359 const_reference operator[](size_t n) const noexcept { return arr_[n]; }
360
365 reference at(size_t n) {
366 rangeCheck(n);
367 return arr_[n];
368 }
369
374 const_reference at(size_t n) const {
375 rangeCheck(n);
376 return arr_[n];
377 }
378
383 reference front() noexcept { return arr_[0]; }
384
389 const_reference front() const noexcept { return arr_[0]; }
390
395 reference back() noexcept { return arr_[size_ - 1]; }
396
401 const_reference back() const noexcept { return arr_[size_ - 1]; }
402
403 // Iterators
404
405 iterator begin() noexcept { return iterator(arr_); }
406 iterator end() noexcept { return iterator(arr_ + size_); }
407
409 const_iterator end() const noexcept { return const_iterator(arr_ + size_); }
410
412 const_iterator cend() const noexcept { return const_iterator(arr_ + size_); }
413
414 // reverse_iterator rbegin() ....
415
416 bool operator==(const Vector& other) const {
417 return size() == other.size()
418 && equal(begin(), end(), other.begin());
419 }
420
421 auto operator<=>(const Vector& other) const {
423 other.begin(), other.end());
424 }
425
426private:
432 void rangeCheck(size_t index) const {
433 if (index >= size_) throw std::out_of_range("Vector index out of range.");
434 }
435
454 template <InputIterator Iter>
455 void rangeInit(Iter first, Iter last) {
456 using iter_category = IteratorTraits<Iter>::iterator_category;
457
458 // If able to find the length of the range - do it even for O(last - first),
459 // because otherwise we must emplaceBack one by one doing many memory
460 // reallocations.
462 rangeInitFastImpl(first, last);
463 } else rangeInitSlowImpl(first, last);
464 }
465
467 template <InputIterator Iter>
468 void rangeInitFastImpl(Iter first, Iter last) {
469 // The key point here: we use capability of the forward iterator to be
470 // readed/incremented multiple times.
471 size_t n = distance(first, last);
472 reserve(n);
473
474 try {
475 // Use size_ field for proper clear() in case of exception
476 for (; size_ < capacity_; ++size_, ++first) {
477 AllocTraits::construct(alloc_, arr_ + size_, *first);
478 }
479 } catch (...) {
480 clear();
481 throw;
482 }
483 }
484
486 template <InputIterator Iter>
487 void rangeInitSlowImpl(Iter first, Iter last) {
488 try {
489 for (; first != last; ++first) emplaceBack(*first);
490 } catch (...) {
491 // If exception in copy c-tor - clear all constructed elements
492 clear();
493 throw;
494 }
495 }
496
517 template <typename... Args>
518 void pushingRealloc(size_t new_capacity, PushDecide do_push,
519 Args&&... args) {
520 // NOTE: Order of operations does matter.
521 // Consider case v.emplaceBack(v[5]). If we firstly
522 // move all elements, then construct new value at the end -
523 // we miss this v[5] reference, since it could be moved. Thefore, we
524 // firstly must construct new value and only then move others.
525 //
526 // NOTE: We don't use move_if_noexcept, instead we always move.
527
528 if (new_capacity <= capacity_) return;
529
530 T* new_arr = nullptr;
531
532 // Need for catch clause to destroy already constructed elements
533 size_t index = 0;
534
535 // Shows whether a new element was constructed
536 bool was_new_pushed = false;
537
538 new_arr = AllocTraits::template allocate<value_type>(alloc_, new_capacity);
539
540 try {
541 // Firstly push a new element
542 if (do_push == PushDecide::kDoPush) {
543 AllocTraits::construct(alloc_, new_arr + size_,
544 forward<Args>(args)...);
545 was_new_pushed = true;
546 }
547
548 // Then move all existing ones
549 for (; index < size_; ++index) {
550 // WARNING: If the template type has excplicitly
551 // delete move c-tor, there's CE. Compile time check
552 // through is_move_constructible concept will fix this. But
553 // the same requires in the several places in the code. So
554 // for now stay with this behaviour.
555 AllocTraits::construct(alloc_, new_arr + index,
556 move(arr_[index]));
557 }
558
559 } catch (...) {
560 // Exception may occur either while pushing new element
561 // or while coping/moving old elements. In the 1-st case do nothing.
562 // In the 2-nd case destroy copied/moved elements.
563
564 if (was_new_pushed) {
565 AllocTraits::destroy(alloc_, new_arr + size_);
566 }
567
568 for (size_t old_index = 0; old_index < index; ++old_index) {
570 }
572
573 throw;
574 }
575
576 // In success destroy and deallocate previous storage.
577 // Don't forget save new size, since clear will nullify all fields.
578 size_t new_size = size_ + (was_new_pushed ? 1 : 0);
579
580 clear();
581
582 arr_ = new_arr;
583 size_ = new_size;
584 capacity_ = new_capacity;
585 }
586
587 pointer arr_ = nullptr;
588 size_t size_ = 0;
589 size_t capacity_ = 0;
590
592 Allocator alloc_;
593};
594
595// Explicit template deduction guide
596template <InputIterator Iter, typename Allocator = PrimitiveAllocator>
598
599} // namespace bmb
Unifined interface for allocators.
Definition allocator.h:69
static void deallocate(Alloc &alloc, T *ptr, size_t n)
Deallocates memory under ptr through allocator.
Definition allocator.h:140
static void destroy(Alloc &alloc, T *ptr)
Destroys object throgh alloctor if destroy method is present. Otherwise calls ~T()
Definition allocator.h:107
static void construct(Alloc &alloc, T *ptr, Args &&... args)
Forwards given arguments to allocator's construct method if present. Otherwise uses placement new.
Definition allocator.h:85
AllocatorAware dynamic array structure.
Definition vector.h:37
detail::BaseVectorIter< T, true > const_iterator
Definition vector.h:59
~Vector()
Definition vector.h:178
pointer getRawData() noexcept
Definition vector.h:342
bool isEmpty() const noexcept
Checks whether Vector has elements.
Definition vector.h:205
size_t capacity() const noexcept
Definition vector.h:337
void boundPopBack()
Destroyes the last object in the array.
Definition vector.h:302
const value_type & const_reference
Definition vector.h:53
Allocator allocator_type
Definition vector.h:57
const_reference operator[](size_t n) const noexcept
Definition vector.h:359
const_pointer getRawData() const noexcept
Definition vector.h:347
const_iterator cbegin() const noexcept
Definition vector.h:411
size_t size() const noexcept
Definition vector.h:332
void reserve(size_t new_capacity)
Preallocates 'new_capacity' memory.
Definition vector.h:223
reference front() noexcept
Definition vector.h:383
const value_type * const_pointer
Definition vector.h:55
auto operator<=>(const Vector &other) const
Definition vector.h:421
size_t size_type
Definition vector.h:56
iterator end() noexcept
Definition vector.h:406
const_reference back() const noexcept
Definition vector.h:401
reference emplaceBack(Args &&... args)
Constructs new element from given args.
Definition vector.h:255
Vector(const Allocator &alloc)
Definition vector.h:63
detail::BaseVectorIter< T, false > iterator
Definition vector.h:58
const_reference front() const noexcept
Definition vector.h:389
const_iterator end() const noexcept
Definition vector.h:409
void popBack() noexcept
Destroyes the last object in the array.
Definition vector.h:290
bool operator==(const Vector &other) const
Definition vector.h:416
Vector(Iter first, Iter last, const Allocator &alloc=Allocator())
Initialize Vector with the given range.
Definition vector.h:116
Vector & operator=(Vector other)
Copy and Move assignment operator.
Definition vector.h:173
Vector(std::initializer_list< value_type > init_list, const Allocator &alloc=Allocator())
Initialize Vector with the given initializer_list.
Definition vector.h:134
reference back() noexcept
Definition vector.h:395
const_iterator cend() const noexcept
Definition vector.h:412
value_type & reference
Definition vector.h:52
reference at(size_t n)
Definition vector.h:365
const_iterator begin() const noexcept
Definition vector.h:408
iterator begin() noexcept
Definition vector.h:405
reference operator[](size_t n) noexcept
Definition vector.h:353
Vector(Vector &&other) noexcept(noexcept(Allocator(move(other.alloc_))))
Definition vector.h:152
Vector(size_t count, const value_type &value=value_type(), const Allocator &alloc=Allocator())
Constructs count elements with given value.
Definition vector.h:80
value_type * pointer
Definition vector.h:54
Vector(const Vector &other)
Copy constructor.
Definition vector.h:147
friend void swap(Vector &a, Vector &b)
Efficiently swaps data of the Vectors.
Definition vector.h:191
Vector() noexcept(noexcept(Allocator()))
Definition vector.h:61
void pushBack(value_type value)
Wrapper for emplaceBack. See docs for emplaceBack.
Definition vector.h:228
void clear() noexcept
Empties the Vector.
Definition vector.h:318
const_reference at(size_t n) const
Definition vector.h:374
T value_type
Definition vector.h:51
contiguous iterator for Vector. Trivial iterator-wrapper for pointer to array(aka T*)
Definition vector_iterator.h:22
Definition algo_base.h:14
constexpr remove_ref_t< T > && move(T &&value) noexcept
Convert a value to xvalue.
Definition move.h:19
bool equal(Iter1 first1, Iter1 last1, Iter2 first2, Pred pred=Pred())
Checks whether given ranges are equal.
Definition algo_base.h:77
auto distance(Iter first, Iter last) -> IteratorTraits< Iter >::difference_type
Returns the number of elements in [first, last).
Definition iterators.h:135
constexpr T && forward(remove_ref_t< T > &value) noexcept
Forward a lvalue.
Definition move.h:33
auto lexicographical_compare_three_way(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2, Cmp cmp=Cmp())
Compares ranges lexicographically using C++20 three-way comparation.
Definition algo_base.h:37