quic/common/CircularDeque.h (315 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include <initializer_list> #include <boost/iterator/iterator_facade.hpp> #include <folly/Portability.h> #include <folly/memory/Malloc.h> #include <glog/logging.h> namespace quic { /** * A container backed by contiguous memory. It can pop and push from both ends * like std::deque. Doing such operation with CircularDeque should be faster * than std::deque. It also supports APIs to mutate the middle of the container * (erase(), emplace() and insert() all support arbitrary positions). But doing * so with CircularDeque is much slower than std::deque. */ template <typename T> struct CircularDeque { using value_type = T; using size_type = std::size_t; using reference = T&; using const_reference = const T&; using difference_type = std::ptrdiff_t; CircularDeque() = default; CircularDeque(size_type n) { resize(n); } CircularDeque(std::initializer_list<T> init); CircularDeque(const CircularDeque& other) { *this = other; } // Move constructor will leave other in a default-initialized state. CircularDeque(CircularDeque&& other) noexcept { swap(other); } CircularDeque& operator=(const CircularDeque& other) { clear(); resize(other.size()); std::uninitialized_copy(other.begin(), other.end(), storage_); end_ = other.size(); return *this; } // Move assignment will leave other in a default-initialized state. CircularDeque& operator=(CircularDeque&& other) noexcept { swap(other); CircularDeque{}.swap(other); return *this; } CircularDeque& operator=(std::initializer_list<T> ilist); ~CircularDeque() { if (capacity_ == 0) { DCHECK_EQ(storage_, nullptr); return; } clear(); folly::sizedFree(storage_, capacity_ * sizeof(T)); capacity_ = 0; } // Missing: more constructor overloads, and custom Allocator // Missing: comparison operators... I'm lazy. I'm waiting for my spaceship. // Iterator template <typename U> class CircularDequeIterator : public boost::iterator_facade< CircularDequeIterator<U>, U, boost::random_access_traversal_tag> { public: CircularDequeIterator(const CircularDeque<U>* deque, size_type index) : deque_(deque), index_(index) {} FOLLY_NODISCARD U& dereference() const { return const_cast<U&>(deque_->storage_[index_]); } FOLLY_NODISCARD bool equal(const CircularDequeIterator<U>& other) const { return deque_ == other.deque_ && index_ == other.index_; } void increment() { ++index_; auto maxSize = deque_->capacity_; if (index_ > maxSize) { index_ = 0; } if (wrapped() && index_ == maxSize) { index_ = 0; } } void decrement() { auto maxSize = deque_->capacity_; if (index_ == 0) { index_ = wrapped() ? maxSize - 1 : maxSize; } else { --index_; } } void advance(difference_type n) { if (n == 0) { return; } if (n > 0) { auto maxSize = deque_->capacity_; index_ = (index_ + n) % (wrapped() ? maxSize : maxSize + 1); } else { while (n++ != 0) { decrement(); } } } FOLLY_NODISCARD difference_type distance_to(const CircularDequeIterator<U>& other) const { if (index_ == other.index_) { return 0; } bool backward = false; if (wrapped()) { if ((index_ >= deque_->begin_ && other.index_ >= deque_->begin_) || (index_ <= deque_->end_ && other.index_ <= deque_->end_)) { backward = index_ > other.index_; } else { backward = index_ < other.index_; } } else { backward = index_ > other.index_; } difference_type counter = 0; auto iter = *this; if (backward) { while (iter-- != other) { --counter; } } else { while (iter++ != other) { ++counter; } } return counter; } private: friend class boost::iterator_core_access; friend struct CircularDeque<U>; friend struct CircularDeque<typename std::remove_const<U>::type>; FOLLY_NODISCARD inline bool wrapped() const { return deque_->begin_ > deque_->end_; } const CircularDeque<U>* deque_; size_type index_; }; using iterator = CircularDequeIterator<T>; using const_iterator = CircularDequeIterator<T>; using reverse_iterator = std::reverse_iterator<iterator>; using const_reverse_iterator = std::reverse_iterator<const_iterator>; FOLLY_NODISCARD bool empty() const noexcept; FOLLY_NODISCARD size_type size() const noexcept; FOLLY_NODISCARD size_type max_size() const noexcept; void resize(size_type count); // Missing compared to std::deque: // resize(size_t, const T&); // shrink_to_fit(); const_reference operator[](size_type index) const; reference operator[](size_type index); FOLLY_NODISCARD const_reference at(size_type index) const; FOLLY_NODISCARD reference at(size_type index); FOLLY_NODISCARD const_reference front() const; FOLLY_NODISCARD reference front(); FOLLY_NODISCARD const_reference back() const; FOLLY_NODISCARD reference back(); FOLLY_NODISCARD iterator begin() noexcept; FOLLY_NODISCARD const_iterator begin() const noexcept; FOLLY_NODISCARD iterator end() noexcept; FOLLY_NODISCARD const_iterator end() const noexcept; FOLLY_NODISCARD const_iterator cbegin() const noexcept; FOLLY_NODISCARD const_iterator cend() const noexcept; FOLLY_NODISCARD reverse_iterator rbegin() noexcept; FOLLY_NODISCARD const_reverse_iterator rbegin() const noexcept; FOLLY_NODISCARD const_reverse_iterator crbegin() const noexcept; FOLLY_NODISCARD reverse_iterator rend() noexcept; FOLLY_NODISCARD const_reverse_iterator rend() const noexcept; FOLLY_NODISCARD const_reverse_iterator crend() const noexcept; template <class... Args> reference emplace_front(Args&&... args); template <class... Args> reference emplace_back(Args&&... args); template <class... Args> iterator emplace(const_iterator pos, Args&&... args); void push_front(const T& val); void push_front(T&& val); void push_back(const T& val); void push_back(T&& val); iterator insert(const_iterator pos, const T& val); iterator insert(const_iterator pos, T&& val); // Missing: a couple insert() overloads void pop_front(); void pop_back(); // If you are erasing from either end of the container and is only erasing // one element, I suggest just use pop_front and pop_back. And if are erasing // the whole container, please use clear(). There is nothing wrong with // erase() in those cases, but such code are behind an UNLIKELY annotation. // The generated code can be slow. iterator erase(const_iterator pos); iterator erase(const_iterator first, const_iterator last); void clear() noexcept; void swap(CircularDeque<T>& other) noexcept; private: FOLLY_NODISCARD bool needSpace() const noexcept; template < typename U = T, typename Iterator, std::enable_if_t<std::is_move_assignable<U>::value, int> = 0> void moveOrCopy(Iterator first, Iterator last, Iterator destFirst) noexcept( std::is_nothrow_move_assignable<T>::value) { if (first == last || first == destFirst) { return; } auto iter = first; while (iter != last) { *destFirst = std::move(*iter++); // Different from iter, destFirst cannot reuse increment/advance. if (destFirst.index_ == capacity_ - 1) { destFirst.index_ = 0; } else { ++destFirst.index_; } } } template < typename U = T, typename ConstIterator, typename Iterator, std::enable_if_t< !std::is_move_assignable<U>::value && std::is_copy_assignable<U>::value, int> = 0> void moveOrCopy( ConstIterator first, ConstIterator last, Iterator destFirst) noexcept(std::is_nothrow_copy_assignable<T>::value) { static_assert(!std::is_assignable< ConstIterator, decltype(*std::declval<ConstIterator>)>::value); if (first == last || first == destFirst) { return; } auto iter = first; while (iter != last) { *destFirst = *iter++; // Different from iter, destFirst cannot reuse increment/advance. if (destFirst.index_ == capacity_ - 1) { destFirst.index_ = 0; } else { ++destFirst.index_; } } } template < typename U = T, typename Iterator, std::enable_if_t<std::is_move_assignable<U>::value, int> = 0> void reverseMoveOrCopy( Iterator first, Iterator last, Iterator destLast) noexcept(std::is_nothrow_move_assignable<T>::value) { if (first == last || last == destLast) { return; } auto iter = last; while (iter != first) { if (destLast.index_ == 0) { destLast.index_ = capacity_ - 1; } else { --destLast.index_; } *destLast = std::move(*--iter); } } template < typename U = T, typename ConstIterator, typename Iterator, std::enable_if_t< !std::is_move_assignable<U>::value && std::is_copy_assignable<U>::value, int> = 0> void reverseMoveOrCopy( ConstIterator first, ConstIterator last, Iterator destLast) noexcept(std::is_nothrow_copy_assignable<T>::value) { static_assert(!std::is_assignable< ConstIterator, decltype(*std::declval<ConstIterator>)>::value); if (first == last || last == destLast) { return; } auto iter = last; while (iter != first) { if (destLast.index_ == 0) { destLast.index_ = capacity_ - 1; } else { --destLast.index_; } *destLast = *--iter; } } template < typename U = T, std::enable_if_t<std::is_move_constructible<U>::value, int> = 0> void allocateWithValueFrom(iterator source, iterator dest) noexcept( std::is_nothrow_move_constructible<T>::value) { new (&storage_[dest.index_]) T(std::move(*source)); } template < typename U = T, std::enable_if_t< !std::is_move_constructible<U>::value && std::is_copy_constructible<U>::value, int> = 0> void allocateWithValueFrom(const_iterator source, iterator dest) noexcept( std::is_nothrow_copy_constructible<T>::value) { new (&storage_[dest.index_]) T(*source); } size_type wrappedDistance( const_iterator first, const_iterator last) noexcept { if (last.index_ >= first.index_) { return last.index_ - first.index_; } return capacity_ - first.index_ + last.index_; } void indexSanityCheck(const_iterator iter) noexcept { if (begin_ <= end_) { DCHECK(begin_ <= iter.index_ && iter.index_ <= end_); } else { DCHECK(iter.index_ >= begin_ || iter.index_ <= end_); } } private: T* storage_ = nullptr; size_type capacity_ = 0; size_type begin_ = 0; size_type end_ = 0; }; } // namespace quic #include <quic/common/CircularDeque-inl.h>