/* * Copyright 2019 The libgav1 Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // libgav1::Vector implementation #ifndef LIBGAV1_SRC_UTILS_VECTOR_H_ #define LIBGAV1_SRC_UTILS_VECTOR_H_ #include #include #include #include #include #include #include #include #include "src/utils/compiler_attributes.h" namespace libgav1 { namespace internal { static constexpr size_t kMinVectorAllocation = 16; // Returns the smallest power of two greater or equal to 'value'. inline size_t NextPow2(size_t value) { if (value == 0) return 0; --value; for (size_t i = 1; i < sizeof(size_t) * 8; i *= 2) value |= value >> i; return value + 1; } // Returns the smallest capacity greater or equal to 'value'. inline size_t NextCapacity(size_t value) { if (value == 0) return 0; if (value <= kMinVectorAllocation) return kMinVectorAllocation; return NextPow2(value); } //------------------------------------------------------------------------------ // Data structure equivalent to std::vector but returning false and to its last // valid state on memory allocation failure. // std::vector with a custom allocator does not fill this need without // exceptions. template class VectorBase { public: using iterator = T*; using const_iterator = const T*; VectorBase() noexcept = default; // Move only. VectorBase(const VectorBase&) = delete; VectorBase& operator=(const VectorBase&) = delete; VectorBase(VectorBase&& other) noexcept : items_(other.items_), capacity_(other.capacity_), num_items_(other.num_items_) { other.items_ = nullptr; other.capacity_ = 0; other.num_items_ = 0; } VectorBase& operator=(VectorBase&& other) noexcept { if (this != &other) { clear(); free(items_); items_ = other.items_; capacity_ = other.capacity_; num_items_ = other.num_items_; other.items_ = nullptr; other.capacity_ = 0; other.num_items_ = 0; } return *this; } ~VectorBase() { clear(); free(items_); } // Reallocates just enough memory if needed so that 'new_cap' items can fit. LIBGAV1_MUST_USE_RESULT bool reserve(size_t new_cap) { if (capacity_ < new_cap) { T* const new_items = static_cast(malloc(new_cap * sizeof(T))); if (new_items == nullptr) return false; if (num_items_ > 0) { if (std::is_trivial::value) { // Cast |new_items| and |items_| to void* to avoid the GCC // -Wclass-memaccess warning and additionally the // bugprone-undefined-memory-manipulation clang-tidy warning. The // memcpy is safe because T is a trivial type. memcpy(static_cast(new_items), static_cast(items_), num_items_ * sizeof(T)); } else { for (size_t i = 0; i < num_items_; ++i) { new (&new_items[i]) T(std::move(items_[i])); items_[i].~T(); } } } free(items_); items_ = new_items; capacity_ = new_cap; } return true; } // Reallocates less memory so that only the existing items can fit. bool shrink_to_fit() { if (capacity_ == num_items_) return true; if (num_items_ == 0) { free(items_); items_ = nullptr; capacity_ = 0; return true; } const size_t previous_capacity = capacity_; capacity_ = 0; // Force reserve() to allocate and copy. if (reserve(num_items_)) return true; capacity_ = previous_capacity; return false; } // Constructs a new item by copy constructor. May reallocate if // 'resize_if_needed'. LIBGAV1_MUST_USE_RESULT bool push_back(const T& value, bool resize_if_needed = true) { if (num_items_ >= capacity_ && (!resize_if_needed || !reserve(internal::NextCapacity(num_items_ + 1)))) { return false; } new (&items_[num_items_]) T(value); ++num_items_; return true; } // Constructs a new item by copy constructor. reserve() must have been called // with a sufficient capacity. // // WARNING: No error checking is performed. void push_back_unchecked(const T& value) { assert(num_items_ < capacity_); new (&items_[num_items_]) T(value); ++num_items_; } // Constructs a new item by move constructor. May reallocate if // 'resize_if_needed'. LIBGAV1_MUST_USE_RESULT bool push_back(T&& value, bool resize_if_needed = true) { if (num_items_ >= capacity_ && (!resize_if_needed || !reserve(internal::NextCapacity(num_items_ + 1)))) { return false; } new (&items_[num_items_]) T(std::move(value)); ++num_items_; return true; } // Constructs a new item by move constructor. reserve() must have been called // with a sufficient capacity. // // WARNING: No error checking is performed. void push_back_unchecked(T&& value) { assert(num_items_ < capacity_); new (&items_[num_items_]) T(std::move(value)); ++num_items_; } // Constructs a new item in place by forwarding the arguments args... to the // constructor. May reallocate. template LIBGAV1_MUST_USE_RESULT bool emplace_back(Args&&... args) { if (num_items_ >= capacity_ && !reserve(internal::NextCapacity(num_items_ + 1))) { return false; } new (&items_[num_items_]) T(std::forward(args)...); ++num_items_; return true; } // Destructs the last item. void pop_back() { --num_items_; items_[num_items_].~T(); } // Destructs the item at 'pos'. void erase(iterator pos) { erase(pos, pos + 1); } // Destructs the items in [first,last). void erase(iterator first, iterator last) { for (iterator it = first; it != last; ++it) it->~T(); if (last != end()) { if (std::is_trivial::value) { // Cast |first| and |last| to void* to avoid the GCC // -Wclass-memaccess warning and additionally the // bugprone-undefined-memory-manipulation clang-tidy warning. The // memmove is safe because T is a trivial type. memmove(static_cast(first), static_cast(last), (end() - last) * sizeof(T)); } else { for (iterator it_src = last, it_dst = first; it_src != end(); ++it_src, ++it_dst) { new (it_dst) T(std::move(*it_src)); it_src->~T(); } } } num_items_ -= std::distance(first, last); } // Destructs all the items. void clear() { erase(begin(), end()); } // Destroys (including deallocating) all the items. void reset() { clear(); if (!shrink_to_fit()) assert(false); } // Accessors bool empty() const { return (num_items_ == 0); } size_t size() const { return num_items_; } size_t capacity() const { return capacity_; } T* data() { return items_; } T& front() { return items_[0]; } T& back() { return items_[num_items_ - 1]; } T& operator[](size_t i) { return items_[i]; } T& at(size_t i) { return items_[i]; } const T* data() const { return items_; } const T& front() const { return items_[0]; } const T& back() const { return items_[num_items_ - 1]; } const T& operator[](size_t i) const { return items_[i]; } const T& at(size_t i) const { return items_[i]; } iterator begin() { return &items_[0]; } const_iterator begin() const { return &items_[0]; } iterator end() { return &items_[num_items_]; } const_iterator end() const { return &items_[num_items_]; } void swap(VectorBase& b) { // Although not necessary here, adding "using std::swap;" and then calling // swap() without namespace qualification is recommended. See Effective // C++, Item 25. using std::swap; swap(items_, b.items_); swap(capacity_, b.capacity_); swap(num_items_, b.num_items_); } protected: T* items_ = nullptr; size_t capacity_ = 0; size_t num_items_ = 0; }; } // namespace internal //------------------------------------------------------------------------------ // Vector class that does *NOT* construct the content on resize(). // Should be reserved to plain old data. template class VectorNoCtor : public internal::VectorBase { public: // Creates or destructs items so that 'new_num_items' exist. // Allocated memory grows every power-of-two items. LIBGAV1_MUST_USE_RESULT bool resize(size_t new_num_items) { using super = internal::VectorBase; if (super::num_items_ < new_num_items) { if (super::capacity_ < new_num_items) { if (!super::reserve(internal::NextCapacity(new_num_items))) { return false; } } super::num_items_ = new_num_items; } else { while (super::num_items_ > new_num_items) { --super::num_items_; super::items_[super::num_items_].~T(); } } return true; } }; // This generic vector class will call the constructors. template class Vector : public internal::VectorBase { public: // Constructs or destructs items so that 'new_num_items' exist. // Allocated memory grows every power-of-two items. LIBGAV1_MUST_USE_RESULT bool resize(size_t new_num_items) { using super = internal::VectorBase; if (super::num_items_ < new_num_items) { if (super::capacity_ < new_num_items) { if (!super::reserve(internal::NextCapacity(new_num_items))) { return false; } } while (super::num_items_ < new_num_items) { new (&super::items_[super::num_items_]) T(); ++super::num_items_; } } else { while (super::num_items_ > new_num_items) { --super::num_items_; super::items_[super::num_items_].~T(); } } return true; } }; //------------------------------------------------------------------------------ // Define non-member swap() functions in the namespace in which VectorNoCtor // and Vector are implemented. See Effective C++, Item 25. template void swap(VectorNoCtor& a, VectorNoCtor& b) { a.swap(b); } template void swap(Vector& a, Vector& b) { a.swap(b); } //------------------------------------------------------------------------------ } // namespace libgav1 #endif // LIBGAV1_SRC_UTILS_VECTOR_H_