quic/common/CircularDeque-inl.h (371 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. */ #include <algorithm> #include <iterator> #include <folly/Likely.h> #include <folly/ScopeGuard.h> namespace quic { constexpr size_t kInitCapacity = 500; constexpr size_t kResizeFactor = 2; template <typename T> CircularDeque<T>::CircularDeque(std::initializer_list<T> init) { *this = std::move(init); } template <typename T> CircularDeque<T>& CircularDeque<T>::operator=(std::initializer_list<T> ilist) { clear(); if (ilist.size() > max_size()) { resize(ilist.size()); } std::uninitialized_copy(ilist.begin(), ilist.end(), storage_); end_ = ilist.size(); return *this; } template <typename T> bool CircularDeque<T>::needSpace() const noexcept { /** * size() and capacity can't be eq. Otherwise begin_ and end_ may point to the * same position, in which case I don't know if my container is full or empty. */ DCHECK_LE(size(), max_size()); return size() == max_size(); } template <typename T> bool CircularDeque<T>::empty() const noexcept { return begin_ == end_; } template <typename T> typename CircularDeque<T>::size_type CircularDeque<T>::size() const noexcept { return end_ - begin_ + (end_ < begin_ ? capacity_ : 0); } template <typename T> typename CircularDeque<T>::size_type CircularDeque<T>::max_size() const noexcept { // See the comments in resize() to see why this needs to minus 1. return capacity_ == 0 ? 0 : capacity_ - 1; } template <typename T> void CircularDeque<T>::resize(size_type count) { if (max_size() == count) { return; } // The way we wrap around begin_ and end_ means for a vector of size S, we can // only store (S - 1) elements in them. auto newCapacity = count + 1; auto newSize = std::min(count, size()); auto newStorage = reinterpret_cast<T*>(folly::checkedMalloc(newCapacity * sizeof(T))); SCOPE_FAIL { folly::sizedFree(newStorage, newCapacity * sizeof(T)); }; if constexpr (std::is_move_constructible_v<T>) { std::uninitialized_move(begin(), end(), newStorage); } else { std::uninitialized_copy(begin(), end(), newStorage); } CircularDeque{}.swap(*this); storage_ = newStorage; capacity_ = newCapacity; end_ = newSize; } template <typename T> typename CircularDeque<T>::const_reference CircularDeque<T>::operator[]( size_type index) const { return *(begin() + index); } template <typename T> typename CircularDeque<T>::reference CircularDeque<T>::operator[]( size_type index) { return *(begin() + index); } template <typename T> typename CircularDeque<T>::const_reference CircularDeque<T>::at( size_type index) const { if (index >= size()) { throw std::out_of_range("Out of bound access"); } return operator[](index); } template <typename T> typename CircularDeque<T>::reference CircularDeque<T>::at(size_type index) { if (index >= size()) { throw std::out_of_range("Out of bound access"); } return operator[](index); } template <typename T> typename CircularDeque<T>::const_reference CircularDeque<T>::front() const { return storage_[begin_]; } template <typename T> typename CircularDeque<T>::reference CircularDeque<T>::front() { return storage_[begin_]; } template <typename T> typename CircularDeque<T>::const_reference CircularDeque<T>::back() const { return storage_[(end_ == 0 ? capacity_ : end_) - 1]; } template <typename T> typename CircularDeque<T>::reference CircularDeque<T>::back() { return storage_[(end_ == 0 ? capacity_ : end_) - 1]; } template <typename T> typename CircularDeque<T>::iterator CircularDeque<T>::begin() noexcept { return CircularDequeIterator<T>(this, begin_); } template <typename T> typename CircularDeque<T>::const_iterator CircularDeque<T>::begin() const noexcept { return CircularDeque<T>::const_iterator(this, begin_); } template <typename T> typename CircularDeque<T>::iterator CircularDeque<T>::end() noexcept { return CircularDequeIterator<T>(this, end_); } template <typename T> typename CircularDeque<T>::const_iterator CircularDeque<T>::end() const noexcept { return CircularDeque<T>::const_iterator(this, end_); } template <typename T> typename CircularDeque<T>::const_iterator CircularDeque<T>::cbegin() const noexcept { return CircularDeque<T>::const_iterator(this, begin_); } template <typename T> typename CircularDeque<T>::const_iterator CircularDeque<T>::cend() const noexcept { return CircularDeque<T>::const_iterator(this, end_); } template <typename T> typename CircularDeque<T>::reverse_iterator CircularDeque<T>::rbegin() noexcept { return CircularDeque<T>::reverse_iterator(end()); } template <typename T> typename CircularDeque<T>::const_reverse_iterator CircularDeque<T>::rbegin() const noexcept { return CircularDeque<T>::const_reverse_iterator(end()); } template <typename T> typename CircularDeque<T>::reverse_iterator CircularDeque<T>::rend() noexcept { return CircularDeque<T>::reverse_iterator(begin()); } template <typename T> typename CircularDeque<T>::const_reverse_iterator CircularDeque<T>::rend() const noexcept { return CircularDeque<T>::const_reverse_iterator(begin()); } template <typename T> typename CircularDeque<T>::const_reverse_iterator CircularDeque<T>::crbegin() const noexcept { return CircularDeque<T>::const_reverse_iterator(end()); } template <typename T> typename CircularDeque<T>::const_reverse_iterator CircularDeque<T>::crend() const noexcept { return CircularDeque<T>::const_reverse_iterator(begin()); } template <typename T> template <class... Args> typename CircularDeque<T>::reference CircularDeque<T>::emplace_front( Args&&... args) { if (needSpace()) { resize(capacity_ == 0 ? kInitCapacity : capacity_ * kResizeFactor); } if (begin_ == 0) { DCHECK_NE(end_, capacity_ - 1); begin_ = capacity_ - 1; } else { DCHECK_NE(end_, begin_ - 1); --begin_; } new (&storage_[begin_]) T(std::forward<Args>(args)...); DCHECK_NE(begin_, end_); return front(); } template <typename T> template <class... Args> typename CircularDeque<T>::reference CircularDeque<T>::emplace_back( Args&&... args) { if (needSpace()) { resize(capacity_ == 0 ? kInitCapacity : capacity_ * kResizeFactor); } DCHECK_GT(capacity_, 0); if (end_ == capacity_) { end_ = 0; DCHECK_NE(0, begin_); } new (&storage_[end_++]) T(std::forward<Args>(args)...); DCHECK_NE(begin_, end_); return back(); } template <typename T> template <class... Args> typename CircularDeque<T>::iterator CircularDeque<T>::emplace( typename CircularDeque<T>::const_iterator pos, Args&&... args) { // Front and back can take shortcuts. Also the resize() will be taken care of // by the emplace_front() and emplace_back(). auto index = pos.index_; if (index == end_) { emplace_back(std::forward<Args>(args)...); DCHECK_NE(begin_, end_); return CircularDequeIterator<T>(this, end_ == 0 ? capacity_ - 1 : end_ - 1); } if (index == begin_) { emplace_front(std::forward<Args>(args)...); DCHECK_NE(begin_, end_); return begin(); } // Similar to erase(), emplace() in the middle is expensive auto dist = std::distance(begin(), pos); if (needSpace()) { resize(capacity_ * kResizeFactor); // After resize, pos is invalid. We need to find the new pos. pos = begin() + dist; index = pos.index_; } auto distIfMoveFront = wrappedDistance(begin(), pos); auto distIfMoveBack = wrappedDistance(pos, end()); auto lastGoodIndex = capacity_ - 1; if (distIfMoveBack <= distIfMoveFront) { auto prev = CircularDequeIterator<T>(this, end_ == 0 ? lastGoodIndex : end_ - 1); allocateWithValueFrom(prev, end()); reverseMoveOrCopy(pos, end() - 1, end()); storage_[index] = T(std::forward<Args>(args)...); end_ = (end() + 1).index_; } else { auto destIndex = begin_ == 0 ? lastGoodIndex : begin_ - 1; auto destIter = CircularDequeIterator<T>(this, destIndex); allocateWithValueFrom(begin(), destIter); moveOrCopy(begin() + 1, pos, begin()); index = index == 0 ? index = lastGoodIndex : index - 1; storage_[index] = T(std::forward<Args>(args)...); begin_ = destIndex; } // We resized before. They can't be at the same place even if we had to move // end_ forward above. DCHECK_NE(begin_, end_); return CircularDequeIterator<T>(this, index); } template <typename T> void CircularDeque<T>::push_front(const T& val) { emplace_front(val); } template <typename T> void CircularDeque<T>::push_front(T&& val) { emplace_front(std::move(val)); } template <typename T> void CircularDeque<T>::push_back(const T& val) { emplace_back(val); } template <typename T> void CircularDeque<T>::push_back(T&& val) { emplace_back(std::move(val)); } template <typename T> typename CircularDeque<T>::iterator CircularDeque<T>::insert( typename CircularDeque<T>::const_iterator pos, const T& val) { return emplace(pos, val); } template <typename T> typename CircularDeque<T>::iterator CircularDeque<T>::insert( typename CircularDeque<T>::const_iterator pos, T&& val) { return emplace(pos, std::move(val)); } template <typename T> void CircularDeque<T>::pop_front() { storage_[begin_].~T(); // This if branch is actually faster than operator% on the machine I tested. if (++begin_ == capacity_) { begin_ = 0; } } template <typename T> void CircularDeque<T>::pop_back() { if (end_ == 0) { end_ = capacity_; } --end_; storage_[end_].~T(); } template <typename T> typename CircularDeque<T>::iterator CircularDeque<T>::erase( typename CircularDeque<T>::const_iterator pos) { return erase(pos, pos + 1); } template <typename T> typename CircularDeque<T>::iterator CircularDeque<T>::erase( typename CircularDeque<T>::const_iterator first, typename CircularDeque<T>::const_iterator last) { DCHECK_NE(first.index_, capacity_); if (first == last) { return CircularDequeIterator<T>(this, last.index_); } if (begin_ < end_) { DCHECK( begin_ <= first.index_ && first.index_ <= last.index_ && last.index_ <= end_); } else { DCHECK(first.index_ <= end_ || first.index_ >= begin_); DCHECK(last.index_ <= end_ || last.index_ >= begin_); } if (UNLIKELY(wrappedDistance(first, last) == size())) { // if we are erasing everything, just clear() clear(); // The return iterator in this case isn't legit. return end(); } if (first == begin() || last == end()) { // If we are erasing from either end, destructing the member and adjust the // index then we are done. auto iter = first; while (iter != last) { indexSanityCheck(iter); iter++->~T(); } if (first == begin()) { begin_ = last.index_; return CircularDequeIterator<T>(this, last.index_); } else { end_ = first.index_; return CircularDequeIterator<T>(this, first.index_); } } // Erasing from middle is hard. We will need to move some of the remaining // elements to fill up the hole it creates. auto currentSize = size(); auto elemsRemoved = std::distance(first, last); DCHECK_GE(elemsRemoved, 0) << "first=" << first.index_ << ", last=" << last.index_ << ", distance=" << elemsRemoved << ", maxSize=" << max_size() << ", begin=" << begin_ << ", end=" << end_; auto distIfMoveFront = wrappedDistance(cbegin(), first); auto distIfMoveBack = wrappedDistance(last, cend()); if (distIfMoveFront < distIfMoveBack) { auto newBegin = last - (first - begin()); // This needs to go reverse direction in case the source and destination // ranges overlap. reverseMoveOrCopy(begin(), first, last); auto iter = begin(); while (iter != newBegin) { iter++->~T(); } begin_ = newBegin.index_; DCHECK_EQ(size(), currentSize - elemsRemoved) << "size=" << size() << ", currentSize=" << currentSize << ", elemsRemoved=" << elemsRemoved; return CircularDequeIterator<T>(this, last.index_); } moveOrCopy(last, end(), first); auto newEnd = end() - elemsRemoved; auto iter = newEnd; while (iter != end()) { iter++->~T(); } end_ = newEnd.index_; DCHECK(size() == currentSize - elemsRemoved); return CircularDequeIterator<T>(this, first.index_); } template <typename T> void CircularDeque<T>::clear() noexcept { if (empty() || capacity_ == 0) { return; } auto iter = begin(); while (iter != end()) { iter++->~T(); } begin_ = 0; end_ = 0; } template <typename T> void CircularDeque<T>::swap(CircularDeque<T>& other) noexcept { using std::swap; swap(storage_, other.storage_); swap(capacity_, other.capacity_); swap(begin_, other.begin_); swap(end_, other.end_); } } // namespace quic