| // Copyright (c) 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef QUICHE_QUIC_CORE_QUIC_CIRCULAR_DEQUE_H_ |
| #define QUICHE_QUIC_CORE_QUIC_CIRCULAR_DEQUE_H_ |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <cstring> |
| #include <iterator> |
| #include <memory> |
| #include <ostream> |
| #include <type_traits> |
| |
| #include "quic/platform/api/quic_export.h" |
| #include "quic/platform/api/quic_logging.h" |
| |
| namespace quic { |
| |
| // QuicCircularDeque is a STL-style container that is similar to std deque in |
| // API and std::vector in capacity management. The goal is to optimize a common |
| // QUIC use case where we keep adding new elements to the end and removing old |
| // elements from the beginning, under such scenarios, if the container's size() |
| // remain relatively stable, QuicCircularDeque requires little to no memory |
| // allocations or deallocations. |
| // |
| // The implementation, as the name suggests, uses a flat circular buffer to hold |
| // all elements. At any point in time, either |
| // a) All elements are placed in a contiguous portion of this buffer, like a |
| // c-array, or |
| // b) Elements are phycially divided into two parts: the first part occupies the |
| // end of the buffer and the second part occupies the beginning of the |
| // buffer. |
| // |
| // Currently, elements can only be pushed or poped from either ends, it can't be |
| // inserted or erased in the middle. |
| // |
| // TODO(wub): Make memory grow/shrink strategies customizable. |
| template <typename T, |
| size_t MinCapacityIncrement = 3, |
| typename Allocator = std::allocator<T>> |
| class QUIC_NO_EXPORT QuicCircularDeque { |
| using AllocatorTraits = std::allocator_traits<Allocator>; |
| |
| // Pointee is either T or const T. |
| template <typename Pointee> |
| class QUIC_NO_EXPORT basic_iterator { |
| using size_type = typename AllocatorTraits::size_type; |
| |
| public: |
| using iterator_category = std::random_access_iterator_tag; |
| using value_type = typename AllocatorTraits::value_type; |
| using difference_type = typename AllocatorTraits::difference_type; |
| using pointer = Pointee*; |
| using reference = Pointee&; |
| |
| basic_iterator() = default; |
| |
| // A copy constructor if Pointee is T. |
| // A conversion from iterator to const_iterator if Pointee is const T. |
| basic_iterator( |
| const basic_iterator<value_type>& it) // NOLINT(runtime/explicit) |
| : deque_(it.deque_), index_(it.index_) {} |
| |
| // A copy assignment if Pointee is T. |
| // A assignment from iterator to const_iterator if Pointee is const T. |
| basic_iterator& operator=(const basic_iterator<value_type>& it) { |
| if (this != &it) { |
| deque_ = it.deque_; |
| index_ = it.index_; |
| } |
| return *this; |
| } |
| |
| reference operator*() const { return *deque_->index_to_address(index_); } |
| pointer operator->() const { return deque_->index_to_address(index_); } |
| reference operator[](difference_type i) { return *(*this + i); } |
| |
| basic_iterator& operator++() { |
| Increment(); |
| return *this; |
| } |
| |
| basic_iterator operator++(int) { |
| basic_iterator result = *this; |
| Increment(); |
| return result; |
| } |
| |
| basic_iterator operator--() { |
| Decrement(); |
| return *this; |
| } |
| |
| basic_iterator operator--(int) { |
| basic_iterator result = *this; |
| Decrement(); |
| return result; |
| } |
| |
| friend basic_iterator operator+(const basic_iterator& it, |
| difference_type delta) { |
| basic_iterator result = it; |
| result.IncrementBy(delta); |
| return result; |
| } |
| |
| basic_iterator& operator+=(difference_type delta) { |
| IncrementBy(delta); |
| return *this; |
| } |
| |
| friend basic_iterator operator-(const basic_iterator& it, |
| difference_type delta) { |
| basic_iterator result = it; |
| result.IncrementBy(-delta); |
| return result; |
| } |
| |
| basic_iterator& operator-=(difference_type delta) { |
| IncrementBy(-delta); |
| return *this; |
| } |
| |
| friend difference_type operator-(const basic_iterator& lhs, |
| const basic_iterator& rhs) { |
| return lhs.ExternalPosition() - rhs.ExternalPosition(); |
| } |
| |
| friend bool operator==(const basic_iterator& lhs, |
| const basic_iterator& rhs) { |
| return lhs.index_ == rhs.index_; |
| } |
| |
| friend bool operator!=(const basic_iterator& lhs, |
| const basic_iterator& rhs) { |
| return !(lhs == rhs); |
| } |
| |
| friend bool operator<(const basic_iterator& lhs, |
| const basic_iterator& rhs) { |
| return lhs.ExternalPosition() < rhs.ExternalPosition(); |
| } |
| |
| friend bool operator<=(const basic_iterator& lhs, |
| const basic_iterator& rhs) { |
| return !(lhs > rhs); |
| } |
| |
| friend bool operator>(const basic_iterator& lhs, |
| const basic_iterator& rhs) { |
| return lhs.ExternalPosition() > rhs.ExternalPosition(); |
| } |
| |
| friend bool operator>=(const basic_iterator& lhs, |
| const basic_iterator& rhs) { |
| return !(lhs < rhs); |
| } |
| |
| private: |
| basic_iterator(const QuicCircularDeque* deque, size_type index) |
| : deque_(deque), index_(index) {} |
| |
| void Increment() { |
| QUICHE_DCHECK_LE(ExternalPosition() + 1, deque_->size()); |
| index_ = deque_->index_next(index_); |
| } |
| |
| void Decrement() { |
| QUICHE_DCHECK_GE(ExternalPosition(), 1u); |
| index_ = deque_->index_prev(index_); |
| } |
| |
| void IncrementBy(difference_type delta) { |
| if (delta >= 0) { |
| // After increment we are before or at end(). |
| QUICHE_DCHECK_LE(static_cast<size_type>(ExternalPosition() + delta), |
| deque_->size()); |
| } else { |
| // After decrement we are after or at begin(). |
| QUICHE_DCHECK_GE(ExternalPosition(), static_cast<size_type>(-delta)); |
| } |
| index_ = deque_->index_increment_by(index_, delta); |
| } |
| |
| size_type ExternalPosition() const { |
| if (index_ >= deque_->begin_) { |
| return index_ - deque_->begin_; |
| } |
| return index_ + deque_->data_capacity() - deque_->begin_; |
| } |
| |
| friend class QuicCircularDeque; |
| const QuicCircularDeque* deque_ = nullptr; |
| size_type index_ = 0; |
| }; |
| |
| public: |
| using allocator_type = typename AllocatorTraits::allocator_type; |
| using value_type = typename AllocatorTraits::value_type; |
| using size_type = typename AllocatorTraits::size_type; |
| using difference_type = typename AllocatorTraits::difference_type; |
| using reference = value_type&; |
| using const_reference = const value_type&; |
| using pointer = typename AllocatorTraits::pointer; |
| using const_pointer = typename AllocatorTraits::const_pointer; |
| using iterator = basic_iterator<T>; |
| using const_iterator = basic_iterator<const T>; |
| using reverse_iterator = std::reverse_iterator<iterator>; |
| using const_reverse_iterator = std::reverse_iterator<const_iterator>; |
| |
| QuicCircularDeque() : QuicCircularDeque(allocator_type()) {} |
| explicit QuicCircularDeque(const allocator_type& alloc) |
| : allocator_and_data_(alloc) {} |
| |
| QuicCircularDeque(size_type count, |
| const T& value, |
| const Allocator& alloc = allocator_type()) |
| : allocator_and_data_(alloc) { |
| resize(count, value); |
| } |
| |
| explicit QuicCircularDeque(size_type count, |
| const Allocator& alloc = allocator_type()) |
| : allocator_and_data_(alloc) { |
| resize(count); |
| } |
| |
| template < |
| class InputIt, |
| typename = std::enable_if_t<std::is_base_of< |
| std::input_iterator_tag, |
| typename std::iterator_traits<InputIt>::iterator_category>::value>> |
| QuicCircularDeque(InputIt first, |
| InputIt last, |
| const Allocator& alloc = allocator_type()) |
| : allocator_and_data_(alloc) { |
| AssignRange(first, last); |
| } |
| |
| QuicCircularDeque(const QuicCircularDeque& other) |
| : QuicCircularDeque( |
| other, |
| AllocatorTraits::select_on_container_copy_construction( |
| other.allocator_and_data_.allocator())) {} |
| |
| QuicCircularDeque(const QuicCircularDeque& other, const allocator_type& alloc) |
| : allocator_and_data_(alloc) { |
| assign(other.begin(), other.end()); |
| } |
| |
| QuicCircularDeque(QuicCircularDeque&& other) |
| : begin_(other.begin_), |
| end_(other.end_), |
| allocator_and_data_(std::move(other.allocator_and_data_)) { |
| other.begin_ = other.end_ = 0; |
| other.allocator_and_data_.data = nullptr; |
| other.allocator_and_data_.data_capacity = 0; |
| } |
| |
| QuicCircularDeque(QuicCircularDeque&& other, const allocator_type& alloc) |
| : allocator_and_data_(alloc) { |
| MoveRetainAllocator(std::move(other)); |
| } |
| |
| QuicCircularDeque(std::initializer_list<T> init, |
| const allocator_type& alloc = allocator_type()) |
| : QuicCircularDeque(init.begin(), init.end(), alloc) {} |
| |
| QuicCircularDeque& operator=(const QuicCircularDeque& other) { |
| if (this == &other) { |
| return *this; |
| } |
| if (AllocatorTraits::propagate_on_container_copy_assignment::value && |
| (allocator_and_data_.allocator() != |
| other.allocator_and_data_.allocator())) { |
| // Destroy all current elements and blocks with the current allocator, |
| // before switching this to use the allocator propagated from "other". |
| DestroyAndDeallocateAll(); |
| begin_ = end_ = 0; |
| allocator_and_data_ = |
| AllocatorAndData(other.allocator_and_data_.allocator()); |
| } |
| assign(other.begin(), other.end()); |
| return *this; |
| } |
| |
| QuicCircularDeque& operator=(QuicCircularDeque&& other) { |
| if (this == &other) { |
| return *this; |
| } |
| if (AllocatorTraits::propagate_on_container_move_assignment::value) { |
| // Take over the storage of "other", along with its allocator. |
| this->~QuicCircularDeque(); |
| new (this) QuicCircularDeque(std::move(other)); |
| } else { |
| MoveRetainAllocator(std::move(other)); |
| } |
| return *this; |
| } |
| |
| ~QuicCircularDeque() { DestroyAndDeallocateAll(); } |
| |
| void assign(size_type count, const T& value) { |
| ClearRetainCapacity(); |
| reserve(count); |
| for (size_t i = 0; i < count; ++i) { |
| emplace_back(value); |
| } |
| } |
| |
| template < |
| class InputIt, |
| typename = std::enable_if_t<std::is_base_of< |
| std::input_iterator_tag, |
| typename std::iterator_traits<InputIt>::iterator_category>::value>> |
| void assign(InputIt first, InputIt last) { |
| AssignRange(first, last); |
| } |
| |
| void assign(std::initializer_list<T> ilist) { |
| assign(ilist.begin(), ilist.end()); |
| } |
| |
| reference at(size_type pos) { |
| QUICHE_DCHECK(pos < size()) << "pos:" << pos << ", size():" << size(); |
| size_type index = begin_ + pos; |
| if (index < data_capacity()) { |
| return *index_to_address(index); |
| } |
| return *index_to_address(index - data_capacity()); |
| } |
| |
| const_reference at(size_type pos) const { |
| return const_cast<QuicCircularDeque*>(this)->at(pos); |
| } |
| |
| reference operator[](size_type pos) { return at(pos); } |
| |
| const_reference operator[](size_type pos) const { return at(pos); } |
| |
| reference front() { |
| QUICHE_DCHECK(!empty()); |
| return *index_to_address(begin_); |
| } |
| |
| const_reference front() const { |
| return const_cast<QuicCircularDeque*>(this)->front(); |
| } |
| |
| reference back() { |
| QUICHE_DCHECK(!empty()); |
| return *(index_to_address(end_ == 0 ? data_capacity() - 1 : end_ - 1)); |
| } |
| |
| const_reference back() const { |
| return const_cast<QuicCircularDeque*>(this)->back(); |
| } |
| |
| iterator begin() { return iterator(this, begin_); } |
| const_iterator begin() const { return const_iterator(this, begin_); } |
| const_iterator cbegin() const { return const_iterator(this, begin_); } |
| |
| iterator end() { return iterator(this, end_); } |
| const_iterator end() const { return const_iterator(this, end_); } |
| const_iterator cend() const { return const_iterator(this, end_); } |
| |
| reverse_iterator rbegin() { return reverse_iterator(end()); } |
| const_reverse_iterator rbegin() const { |
| return const_reverse_iterator(end()); |
| } |
| const_reverse_iterator crbegin() const { return rbegin(); } |
| |
| reverse_iterator rend() { return reverse_iterator(begin()); } |
| const_reverse_iterator rend() const { |
| return const_reverse_iterator(begin()); |
| } |
| const_reverse_iterator crend() const { return rend(); } |
| |
| size_type capacity() const { |
| return data_capacity() == 0 ? 0 : data_capacity() - 1; |
| } |
| |
| void reserve(size_type new_cap) { |
| if (new_cap > capacity()) { |
| Relocate(new_cap); |
| } |
| } |
| |
| // Remove all elements. Leave capacity unchanged. |
| void clear() { ClearRetainCapacity(); } |
| |
| bool empty() const { return begin_ == end_; } |
| |
| size_type size() const { |
| if (begin_ <= end_) { |
| return end_ - begin_; |
| } |
| return data_capacity() + end_ - begin_; |
| } |
| |
| void resize(size_type count) { ResizeInternal(count); } |
| |
| void resize(size_type count, const value_type& value) { |
| ResizeInternal(count, value); |
| } |
| |
| void push_front(const T& value) { emplace_front(value); } |
| void push_front(T&& value) { emplace_front(std::move(value)); } |
| |
| template <class... Args> |
| reference emplace_front(Args&&... args) { |
| MaybeExpandCapacity(1); |
| begin_ = index_prev(begin_); |
| new (index_to_address(begin_)) T(std::forward<Args>(args)...); |
| return front(); |
| } |
| |
| void push_back(const T& value) { emplace_back(value); } |
| void push_back(T&& value) { emplace_back(std::move(value)); } |
| |
| template <class... Args> |
| reference emplace_back(Args&&... args) { |
| MaybeExpandCapacity(1); |
| new (index_to_address(end_)) T(std::forward<Args>(args)...); |
| end_ = index_next(end_); |
| return back(); |
| } |
| |
| void pop_front() { |
| QUICHE_DCHECK(!empty()); |
| DestroyByIndex(begin_); |
| begin_ = index_next(begin_); |
| MaybeShrinkCapacity(); |
| } |
| |
| size_type pop_front_n(size_type count) { |
| size_type num_elements_to_pop = std::min(count, size()); |
| size_type new_begin = index_increment_by(begin_, num_elements_to_pop); |
| DestroyRange(begin_, new_begin); |
| begin_ = new_begin; |
| MaybeShrinkCapacity(); |
| return num_elements_to_pop; |
| } |
| |
| void pop_back() { |
| QUICHE_DCHECK(!empty()); |
| end_ = index_prev(end_); |
| DestroyByIndex(end_); |
| MaybeShrinkCapacity(); |
| } |
| |
| size_type pop_back_n(size_type count) { |
| size_type num_elements_to_pop = std::min(count, size()); |
| size_type new_end = index_increment_by(end_, -num_elements_to_pop); |
| DestroyRange(new_end, end_); |
| end_ = new_end; |
| MaybeShrinkCapacity(); |
| return num_elements_to_pop; |
| } |
| |
| void swap(QuicCircularDeque& other) { |
| using std::swap; |
| swap(begin_, other.begin_); |
| swap(end_, other.end_); |
| |
| if (AllocatorTraits::propagate_on_container_swap::value) { |
| swap(allocator_and_data_, other.allocator_and_data_); |
| } else { |
| // When propagate_on_container_swap is false, it is undefined behavior, by |
| // c++ standard, to swap between two AllocatorAwareContainer(s) with |
| // unequal allocators. |
| QUICHE_DCHECK(get_allocator() == other.get_allocator()) |
| << "Undefined swap behavior"; |
| swap(allocator_and_data_.data, other.allocator_and_data_.data); |
| swap(allocator_and_data_.data_capacity, |
| other.allocator_and_data_.data_capacity); |
| } |
| } |
| |
| friend void swap(QuicCircularDeque& lhs, QuicCircularDeque& rhs) { |
| lhs.swap(rhs); |
| } |
| |
| allocator_type get_allocator() const { |
| return allocator_and_data_.allocator(); |
| } |
| |
| friend bool operator==(const QuicCircularDeque& lhs, |
| const QuicCircularDeque& rhs) { |
| return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); |
| } |
| |
| friend bool operator!=(const QuicCircularDeque& lhs, |
| const QuicCircularDeque& rhs) { |
| return !(lhs == rhs); |
| } |
| |
| friend QUIC_NO_EXPORT std::ostream& operator<<(std::ostream& os, |
| const QuicCircularDeque& dq) { |
| os << "{"; |
| for (size_type pos = 0; pos != dq.size(); ++pos) { |
| if (pos != 0) { |
| os << ","; |
| } |
| os << " " << dq[pos]; |
| } |
| os << " }"; |
| return os; |
| } |
| |
| private: |
| void MoveRetainAllocator(QuicCircularDeque&& other) { |
| if (get_allocator() == other.get_allocator()) { |
| // Take over the storage of "other", with which we share an allocator. |
| DestroyAndDeallocateAll(); |
| |
| begin_ = other.begin_; |
| end_ = other.end_; |
| allocator_and_data_.data = other.allocator_and_data_.data; |
| allocator_and_data_.data_capacity = |
| other.allocator_and_data_.data_capacity; |
| |
| other.begin_ = other.end_ = 0; |
| other.allocator_and_data_.data = nullptr; |
| other.allocator_and_data_.data_capacity = 0; |
| } else { |
| // We cannot take over of the storage from "other", since it has a |
| // different allocator; we're stuck move-assigning elements individually. |
| ClearRetainCapacity(); |
| for (auto& elem : other) { |
| push_back(std::move(elem)); |
| } |
| other.clear(); |
| } |
| } |
| |
| template < |
| typename InputIt, |
| typename = std::enable_if_t<std::is_base_of< |
| std::input_iterator_tag, |
| typename std::iterator_traits<InputIt>::iterator_category>::value>> |
| void AssignRange(InputIt first, InputIt last) { |
| ClearRetainCapacity(); |
| if (std::is_base_of< |
| std::random_access_iterator_tag, |
| typename std::iterator_traits<InputIt>::iterator_category>::value) { |
| reserve(std::distance(first, last)); |
| } |
| for (; first != last; ++first) { |
| emplace_back(*first); |
| } |
| } |
| |
| // WARNING: begin_, end_ and allocator_and_data_ are not modified. |
| void DestroyAndDeallocateAll() { |
| DestroyRange(begin_, end_); |
| |
| if (data_capacity() > 0) { |
| QUICHE_DCHECK_NE(nullptr, allocator_and_data_.data); |
| AllocatorTraits::deallocate(allocator_and_data_.allocator(), |
| allocator_and_data_.data, data_capacity()); |
| } |
| } |
| |
| void ClearRetainCapacity() { |
| DestroyRange(begin_, end_); |
| begin_ = end_ = 0; |
| } |
| |
| void MaybeShrinkCapacity() { |
| // TODO(wub): Implement a storage policy that actually shrinks. |
| } |
| |
| void MaybeExpandCapacity(size_t num_additional_elements) { |
| size_t new_size = size() + num_additional_elements; |
| if (capacity() >= new_size) { |
| return; |
| } |
| |
| // The minimum amount of additional capacity to grow. |
| size_t min_additional_capacity = |
| std::max(MinCapacityIncrement, capacity() / 4); |
| size_t new_capacity = |
| std::max(new_size, capacity() + min_additional_capacity); |
| |
| Relocate(new_capacity); |
| } |
| |
| void Relocate(size_t new_capacity) { |
| const size_t num_elements = size(); |
| QUICHE_DCHECK_GT(new_capacity, num_elements) |
| << "new_capacity:" << new_capacity << ", num_elements:" << num_elements; |
| |
| size_t new_data_capacity = new_capacity + 1; |
| pointer new_data = AllocatorTraits::allocate( |
| allocator_and_data_.allocator(), new_data_capacity); |
| |
| if (begin_ < end_) { |
| // Not wrapped. |
| RelocateUnwrappedRange(begin_, end_, new_data); |
| } else if (begin_ > end_) { |
| // Wrapped. |
| const size_t num_elements_before_wrap = data_capacity() - begin_; |
| RelocateUnwrappedRange(begin_, data_capacity(), new_data); |
| RelocateUnwrappedRange(0, end_, new_data + num_elements_before_wrap); |
| } |
| |
| if (data_capacity()) { |
| AllocatorTraits::deallocate(allocator_and_data_.allocator(), |
| allocator_and_data_.data, data_capacity()); |
| } |
| |
| allocator_and_data_.data = new_data; |
| allocator_and_data_.data_capacity = new_data_capacity; |
| begin_ = 0; |
| end_ = num_elements; |
| } |
| |
| template <typename T_ = T> |
| typename std::enable_if<std::is_trivially_copyable<T_>::value, void>::type |
| RelocateUnwrappedRange(size_type begin, size_type end, pointer dest) const { |
| QUICHE_DCHECK_LE(begin, end) << "begin:" << begin << ", end:" << end; |
| pointer src = index_to_address(begin); |
| QUICHE_DCHECK_NE(src, nullptr); |
| memcpy(dest, src, sizeof(T) * (end - begin)); |
| DestroyRange(begin, end); |
| } |
| |
| template <typename T_ = T> |
| typename std::enable_if<!std::is_trivially_copyable<T_>::value && |
| std::is_move_constructible<T_>::value, |
| void>::type |
| RelocateUnwrappedRange(size_type begin, size_type end, pointer dest) const { |
| QUICHE_DCHECK_LE(begin, end) << "begin:" << begin << ", end:" << end; |
| pointer src = index_to_address(begin); |
| pointer src_end = index_to_address(end); |
| while (src != src_end) { |
| new (dest) T(std::move(*src)); |
| DestroyByAddress(src); |
| ++dest; |
| ++src; |
| } |
| } |
| |
| template <typename T_ = T> |
| typename std::enable_if<!std::is_trivially_copyable<T_>::value && |
| !std::is_move_constructible<T_>::value, |
| void>::type |
| RelocateUnwrappedRange(size_type begin, size_type end, pointer dest) const { |
| QUICHE_DCHECK_LE(begin, end) << "begin:" << begin << ", end:" << end; |
| pointer src = index_to_address(begin); |
| pointer src_end = index_to_address(end); |
| while (src != src_end) { |
| new (dest) T(*src); |
| DestroyByAddress(src); |
| ++dest; |
| ++src; |
| } |
| } |
| |
| template <class... U> |
| void ResizeInternal(size_type count, U&&... u) { |
| if (count > size()) { |
| // Expanding. |
| MaybeExpandCapacity(count - size()); |
| while (size() < count) { |
| emplace_back(std::forward<U>(u)...); |
| } |
| } else { |
| // Most likely shrinking. No-op if count == size(). |
| size_type new_end = (begin_ + count) % data_capacity(); |
| DestroyRange(new_end, end_); |
| end_ = new_end; |
| |
| MaybeShrinkCapacity(); |
| } |
| } |
| |
| void DestroyRange(size_type begin, size_type end) const { |
| if (std::is_trivially_destructible<T>::value) { |
| return; |
| } |
| if (end >= begin) { |
| DestroyUnwrappedRange(begin, end); |
| } else { |
| DestroyUnwrappedRange(begin, data_capacity()); |
| DestroyUnwrappedRange(0, end); |
| } |
| } |
| |
| // Should only be called from DestroyRange. |
| void DestroyUnwrappedRange(size_type begin, size_type end) const { |
| QUICHE_DCHECK_LE(begin, end) << "begin:" << begin << ", end:" << end; |
| for (; begin != end; ++begin) { |
| DestroyByIndex(begin); |
| } |
| } |
| |
| void DestroyByIndex(size_type index) const { |
| DestroyByAddress(index_to_address(index)); |
| } |
| |
| void DestroyByAddress(pointer address) const { |
| if (std::is_trivially_destructible<T>::value) { |
| return; |
| } |
| address->~T(); |
| } |
| |
| size_type data_capacity() const { return allocator_and_data_.data_capacity; } |
| |
| pointer index_to_address(size_type index) const { |
| return allocator_and_data_.data + index; |
| } |
| |
| size_type index_prev(size_type index) const { |
| return index == 0 ? data_capacity() - 1 : index - 1; |
| } |
| |
| size_type index_next(size_type index) const { |
| return index == data_capacity() - 1 ? 0 : index + 1; |
| } |
| |
| size_type index_increment_by(size_type index, difference_type delta) const { |
| if (delta == 0) { |
| return index; |
| } |
| |
| QUICHE_DCHECK_LT(static_cast<size_type>(std::abs(delta)), data_capacity()); |
| return (index + data_capacity() + delta) % data_capacity(); |
| } |
| |
| // Empty base-class optimization: bundle storage for our allocator together |
| // with the fields we had to store anyway, via inheriting from the allocator, |
| // so this allocator instance doesn't consume any storage when its type has no |
| // data members. |
| struct AllocatorAndData : private allocator_type { |
| explicit AllocatorAndData(const allocator_type& alloc) |
| : allocator_type(alloc) {} |
| |
| const allocator_type& allocator() const { return *this; } |
| allocator_type& allocator() { return *this; } |
| |
| pointer data = nullptr; |
| size_type data_capacity = 0; |
| }; |
| |
| size_type begin_ = 0; |
| size_type end_ = 0; |
| AllocatorAndData allocator_and_data_; |
| }; |
| |
| } // namespace quic |
| |
| #endif // QUICHE_QUIC_CORE_QUIC_CIRCULAR_DEQUE_H_ |