This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenlib/BLI_vector.hh

848 lines
23 KiB
C++
Raw Normal View History

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __BLI_VECTOR_HH__
#define __BLI_VECTOR_HH__
2019-09-13 21:12:26 +10:00
/** \file
* \ingroup bli
*
* A `blender::Vector<T>` is a dynamically growing contiguous array for values of type T. It is
* designed to be a more convenient and efficient replacement for `std::vector`. Note that the term
* "vector" has nothing to do with a vector from computer graphics here.
*
* A vector supports efficient insertion and removal at the end (O(1) amortized). Removal in other
* places takes O(n) time, because all elements afterwards have to be moved. If the order of
* elements is not important, `remove_and_reorder` can be used instead of `remove` for better
* performance.
*
* The improved efficiency is mainly achieved by supporting small buffer optimization. As long as
* the number of elements in the vector does not become larger than InlineBufferCapacity, no memory
* allocation is done. As a consequence, iterators are invalidated when a blender::Vector is moved
* (iterators of std::vector remain valid when the vector is moved).
*
* `blender::Vector` should be your default choice for a vector data structure in Blender.
*/
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include "BLI_allocator.hh"
#include "BLI_index_range.hh"
#include "BLI_listbase_wrapper.hh"
#include "BLI_math_base.h"
#include "BLI_memory_utils.hh"
#include "BLI_span.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
namespace blender {
template<
/**
* Type of the values stored in this vector. It has to be movable.
*/
typename T,
/**
* The number of values that can be stored in this vector, without doing a heap allocation.
* Sometimes it makes sense to increase this value a lot. The memory in the inline buffer is
* not initialized when it is not needed.
*
* When T is large, the small buffer optimization is disabled by default to avoid large
2020-06-13 12:50:07 +10:00
* unexpected allocations on the stack. It can still be enabled explicitly though.
*/
uint InlineBufferCapacity = (sizeof(T) < 100) ? 4 : 0,
/**
* The allocator used by this vector. Should rarely be changed, except when you don't want that
* MEM_* is used internally.
*/
typename Allocator = GuardedAllocator>
class Vector {
private:
/**
2020-06-13 12:50:07 +10:00
* Use pointers instead of storing the size explicitly. This reduces the number of instructions
* in `append`.
*
* The pointers might point to the memory in the inline buffer.
*/
T *begin_;
T *end_;
T *capacity_end_;
/** Used for allocations when the inline buffer is too small. */
Allocator allocator_;
/** A placeholder buffer that will remain uninitialized until it is used. */
AlignedBuffer<(uint)sizeof(T) * InlineBufferCapacity, (uint)alignof(T)> inline_buffer_;
/**
2020-06-13 12:50:07 +10:00
* Store the size of the vector explicitly in debug builds. Otherwise you'd always have to call
* the `size` function or do the math to compute it from the pointers manually. This is rather
* annoying. Knowing the size of a vector is often quite essential when debugging some code.
*/
#ifndef NDEBUG
uint debug_size_;
# define UPDATE_VECTOR_SIZE(ptr) (ptr)->debug_size_ = (uint)((ptr)->end_ - (ptr)->begin_)
#else
# define UPDATE_VECTOR_SIZE(ptr) ((void)0)
#endif
/**
2020-06-30 20:54:31 +10:00
* Be a friend with other vector instantiations. This is necessary to implement some memory
* management logic.
*/
template<typename OtherT, uint OtherInlineBufferCapacity, typename OtherAllocator>
friend class Vector;
public:
/**
* Create an empty vector.
* This does not do any memory allocation.
*/
Vector()
{
begin_ = this->inline_buffer();
end_ = begin_;
capacity_end_ = begin_ + InlineBufferCapacity;
UPDATE_VECTOR_SIZE(this);
}
/**
* Create a vector with a specific size.
* The elements will be default constructed.
* If T is trivially constructible, the elements in the vector are not touched.
*/
explicit Vector(uint size) : Vector()
{
this->resize(size);
}
/**
* Create a vector filled with a specific value.
*/
Vector(uint size, const T &value) : Vector()
{
this->reserve(size);
this->increase_size_by_unchecked(size);
blender::uninitialized_fill_n(begin_, size, value);
}
/**
2020-06-25 23:13:02 +10:00
* Create a vector that contains copies of the values in the initialized list.
*
* This allows you to write code like:
* Vector<int> vec = {3, 4, 5};
*/
Vector(const std::initializer_list<T> &values) : Vector(Span<T>(values))
{
}
/**
* Create a vector from an array ref. The values in the vector are copy constructed.
*/
Vector(Span<T> values) : Vector()
{
2020-07-03 14:52:51 +02:00
const uint size = values.size();
this->reserve(size);
this->increase_size_by_unchecked(size);
blender::uninitialized_copy_n(values.data(), size, begin_);
}
/**
* Create a vector from any container. It must be possible to use the container in a range-for
* loop.
*/
template<typename ContainerT> static Vector FromContainer(const ContainerT &container)
{
Vector vector;
for (const auto &value : container) {
vector.append(value);
}
return vector;
}
/**
* Create a vector from a ListBase. The caller has to make sure that the values in the linked
* list have the correct type.
*
* Example Usage:
* Vector<ModifierData *> modifiers(ob->modifiers);
*/
Vector(ListBase &values) : Vector()
{
LISTBASE_FOREACH (T, value, &values) {
this->append(value);
}
}
/**
* Create a copy of another vector. The other vector will not be changed. If the other vector has
* less than InlineBufferCapacity elements, no allocation will be made.
*/
Vector(const Vector &other) : allocator_(other.allocator_)
{
this->init_copy_from_other_vector(other);
}
/**
* Create a copy of a vector with a different InlineBufferCapacity. This needs to be handled
* separately, so that the other one is a valid copy constructor.
*/
template<uint OtherInlineBufferCapacity>
Vector(const Vector<T, OtherInlineBufferCapacity, Allocator> &other)
: allocator_(other.allocator_)
{
this->init_copy_from_other_vector(other);
}
/**
* Steal the elements from another vector. This does not do an allocation. The other vector will
* have zero elements afterwards.
*/
template<uint OtherInlineBufferCapacity>
Vector(Vector<T, OtherInlineBufferCapacity, Allocator> &&other) noexcept
: allocator_(other.allocator_)
{
2020-07-03 14:52:51 +02:00
const uint size = other.size();
if (other.is_inline()) {
if (size <= InlineBufferCapacity) {
/* Copy between inline buffers. */
begin_ = this->inline_buffer();
end_ = begin_ + size;
capacity_end_ = begin_ + InlineBufferCapacity;
uninitialized_relocate_n(other.begin_, size, begin_);
}
else {
/* Copy from inline buffer to newly allocated buffer. */
2020-07-03 14:52:51 +02:00
const uint capacity = size;
begin_ = (T *)allocator_.allocate(sizeof(T) * capacity, alignof(T), AT);
end_ = begin_ + size;
capacity_end_ = begin_ + capacity;
uninitialized_relocate_n(other.begin_, size, begin_);
}
}
else {
/* Steal the pointer. */
begin_ = other.begin_;
end_ = other.end_;
capacity_end_ = other.capacity_end_;
}
other.begin_ = other.inline_buffer();
other.end_ = other.begin_;
other.capacity_end_ = other.begin_ + OtherInlineBufferCapacity;
UPDATE_VECTOR_SIZE(this);
UPDATE_VECTOR_SIZE(&other);
}
~Vector()
{
destruct_n(begin_, this->size());
if (!this->is_inline()) {
allocator_.deallocate(begin_);
}
}
Vector &operator=(const Vector &other)
{
if (this == &other) {
return *this;
}
this->~Vector();
new (this) Vector(other);
return *this;
}
Vector &operator=(Vector &&other)
{
if (this == &other) {
return *this;
}
/* This can be incorrect, when the vector is used to build a recursive data structure. However,
we don't take care of it at this low level. See https://youtu.be/7Qgd9B1KuMQ?t=840. */
this->~Vector();
new (this) Vector(std::move(other));
return *this;
}
/**
* Get the value at the given index. This invokes undefined behavior when the index is out of
* bounds.
*/
const T &operator[](uint index) const
{
BLI_assert(index < this->size());
return begin_[index];
}
T &operator[](uint index)
{
BLI_assert(index < this->size());
return begin_[index];
}
operator Span<T>() const
{
return Span<T>(begin_, this->size());
}
operator MutableSpan<T>()
{
return MutableSpan<T>(begin_, this->size());
}
Span<T> as_span() const
{
return *this;
}
MutableSpan<T> as_mutable_span()
{
return *this;
}
/**
* Make sure that enough memory is allocated to hold min_capacity elements.
* This won't necessarily make an allocation when min_capacity is small.
* The actual size of the vector does not change.
*/
2020-07-03 14:52:51 +02:00
void reserve(const uint min_capacity)
{
if (min_capacity > this->capacity()) {
this->realloc_to_at_least(min_capacity);
}
}
/**
* Change the size of the vector so that it contains new_size elements.
* If new_size is smaller than the old size, the elements at the end of the vector are
* destructed. If new_size is larger than the old size, the new elements at the end are default
* constructed. If T is trivially constructible, the memory is not touched by this function.
*/
2020-07-03 14:52:51 +02:00
void resize(const uint new_size)
{
2020-07-03 14:52:51 +02:00
const uint old_size = this->size();
if (new_size > old_size) {
this->reserve(new_size);
default_construct_n(begin_ + old_size, new_size - old_size);
}
else {
destruct_n(begin_ + new_size, old_size - new_size);
}
end_ = begin_ + new_size;
UPDATE_VECTOR_SIZE(this);
}
/**
* Change the size of the vector so that it contains new_size elements.
* If new_size is smaller than the old size, the elements at the end of the vector are
* destructed. If new_size is larger than the old size, the new elements will be copy constructed
* from the given value.
*/
2020-07-03 14:52:51 +02:00
void resize(const uint new_size, const T &value)
{
2020-07-03 14:52:51 +02:00
const uint old_size = this->size();
if (new_size > old_size) {
this->reserve(new_size);
uninitialized_fill_n(begin_ + old_size, new_size - old_size, value);
}
else {
destruct_n(begin_ + new_size, old_size - new_size);
}
end_ = begin_ + new_size;
UPDATE_VECTOR_SIZE(this);
}
/**
* Afterwards the vector has 0 elements, but will still have
* memory to be refilled again.
*/
void clear()
{
destruct_n(begin_, this->size());
end_ = begin_;
UPDATE_VECTOR_SIZE(this);
}
/**
* Afterwards the vector has 0 elements and any allocated memory
* will be freed.
*/
void clear_and_make_inline()
{
destruct_n(begin_, this->size());
if (!this->is_inline()) {
allocator_.deallocate(begin_);
}
begin_ = this->inline_buffer();
end_ = begin_;
capacity_end_ = begin_ + InlineBufferCapacity;
UPDATE_VECTOR_SIZE(this);
}
/**
* Insert a new element at the end of the vector.
* This might cause a reallocation with the capacity is exceeded.
*
* This is similar to std::vector::push_back.
*/
void append(const T &value)
{
this->ensure_space_for_one();
this->append_unchecked(value);
}
void append(T &&value)
{
this->ensure_space_for_one();
this->append_unchecked(std::move(value));
}
/**
* Append the value to the vector and return the index that can be used to access the newly
* added value.
*/
uint append_and_get_index(const T &value)
{
2020-07-03 14:52:51 +02:00
const uint index = this->size();
this->append(value);
return index;
}
/**
* Append the value if it is not yet in the vector. This has to do a linear search to check if
* the value is in the vector. Therefore, this should only be called when it is known that the
* vector is small.
*/
void append_non_duplicates(const T &value)
{
if (!this->contains(value)) {
this->append(value);
}
}
/**
* Append the value and assume that vector has enough memory reserved. This invokes undefined
* behavior when not enough capacity has been reserved beforehand. Only use this in performance
* critical code.
*/
void append_unchecked(const T &value)
{
BLI_assert(end_ < capacity_end_);
new (end_) T(value);
end_++;
UPDATE_VECTOR_SIZE(this);
}
void append_unchecked(T &&value)
{
BLI_assert(end_ < capacity_end_);
new (end_) T(std::move(value));
end_++;
UPDATE_VECTOR_SIZE(this);
}
/**
* Insert the same element n times at the end of the vector.
* This might result in a reallocation internally.
*/
2020-07-03 14:52:51 +02:00
void append_n_times(const T &value, const uint n)
{
this->reserve(this->size() + n);
blender::uninitialized_fill_n(end_, n, value);
this->increase_size_by_unchecked(n);
}
/**
* Enlarges the size of the internal buffer that is considered to be initialized. This invokes
* undefined behavior when when the new size is larger than the capacity. The method can be
* useful when you want to call constructors in the vector yourself. This should only be done in
* very rare cases and has to be justified every time.
*/
2020-07-03 14:52:51 +02:00
void increase_size_by_unchecked(const uint n)
{
BLI_assert(end_ + n <= capacity_end_);
end_ += n;
UPDATE_VECTOR_SIZE(this);
}
/**
* Copy the elements of another array to the end of this vector.
*
* This can be used to emulate parts of std::vector::insert.
*/
void extend(Span<T> array)
{
this->extend(array.data(), array.size());
}
void extend(const T *start, uint amount)
{
this->reserve(this->size() + amount);
this->extend_unchecked(start, amount);
}
/**
* Adds all elements from the array that are not already in the vector. This is an expensive
* operation when the vector is large, but can be very cheap when it is known that the vector is
* small.
*/
void extend_non_duplicates(Span<T> array)
{
for (const T &value : array) {
this->append_non_duplicates(value);
}
}
/**
* Extend the vector without bounds checking. It is assumed that enough memory has been reserved
* beforehand. Only use this in performance critical code.
*/
void extend_unchecked(Span<T> array)
{
this->extend_unchecked(array.data(), array.size());
}
void extend_unchecked(const T *start, uint amount)
{
BLI_assert(begin_ + amount <= capacity_end_);
blender::uninitialized_copy_n(start, amount, end_);
end_ += amount;
UPDATE_VECTOR_SIZE(this);
}
/**
* Return a reference to the last element in the vector.
* This will assert when the vector is empty.
*/
const T &last() const
{
BLI_assert(this->size() > 0);
return *(end_ - 1);
}
T &last()
{
BLI_assert(this->size() > 0);
return *(end_ - 1);
}
/**
* Replace every element with a new value.
*/
void fill(const T &value)
{
initialized_fill_n(begin_, this->size(), value);
}
/**
* Copy the value to all positions specified by the indices array.
*/
void fill_indices(Span<uint> indices, const T &value)
{
MutableSpan<T>(*this).fill_indices(indices, value);
}
/**
* Return how many values are currently stored in the vector.
*/
uint size() const
{
BLI_assert(debug_size_ == (uint)(end_ - begin_));
return (uint)(end_ - begin_);
}
/**
* Returns true when the vector contains no elements, otherwise false.
*
* This is the same as std::vector::empty.
*/
bool is_empty() const
{
return begin_ == end_;
}
/**
* Destructs the last element and decreases the size by one. This invokes undefined behavior when
* the vector is empty.
*/
void remove_last()
{
BLI_assert(!this->is_empty());
end_--;
end_->~T();
UPDATE_VECTOR_SIZE(this);
}
/**
* Remove the last element from the vector and return it. This invokes undefined behavior when
* the vector is empty.
*
* This is similar to std::vector::pop_back.
*/
T pop_last()
{
BLI_assert(!this->is_empty());
end_--;
T value = std::move(*end_);
end_->~T();
UPDATE_VECTOR_SIZE(this);
return value;
}
/**
* Delete any element in the vector. The empty space will be filled by the previously last
* element. This takes O(1) time.
*/
2020-07-03 14:52:51 +02:00
void remove_and_reorder(const uint index)
{
BLI_assert(index < this->size());
T *element_to_remove = begin_ + index;
end_--;
if (element_to_remove < end_) {
*element_to_remove = std::move(*end_);
}
end_->~T();
UPDATE_VECTOR_SIZE(this);
}
/**
2020-06-25 23:13:02 +10:00
* Finds the first occurrence of the value, removes it and copies the last element to the hole in
* the vector. This takes O(n) time.
*/
void remove_first_occurrence_and_reorder(const T &value)
{
2020-07-03 14:52:51 +02:00
const uint index = this->first_index_of(value);
this->remove_and_reorder((uint)index);
}
/**
* Remove the element at the given index and move all values coming after it one towards the
* front. This takes O(n) time. If the order is not important, remove_and_reorder should be used
* instead.
*
* This is similar to std::vector::erase.
*/
2020-07-03 14:52:51 +02:00
void remove(const uint index)
{
BLI_assert(index < this->size());
2020-07-03 14:52:51 +02:00
const uint last_index = this->size() - 1;
for (uint i = index; i < last_index; i++) {
begin_[i] = std::move(begin_[i + 1]);
}
begin_[last_index].~T();
end_--;
UPDATE_VECTOR_SIZE(this);
}
/**
* Do a linear search to find the value in the vector.
* When found, return the first index, otherwise return -1.
*/
int first_index_of_try(const T &value) const
{
2020-07-03 14:52:51 +02:00
for (const T *current = begin_; current != end_; current++) {
if (*current == value) {
return (int)(current - begin_);
}
}
return -1;
}
/**
* Do a linear search to find the value in the vector and return the found index. This invokes
* undefined behavior when the value is not in the vector.
*/
uint first_index_of(const T &value) const
{
2020-07-03 14:52:51 +02:00
const int index = this->first_index_of_try(value);
BLI_assert(index >= 0);
return (uint)index;
}
/**
* Do a linear search to see of the value is in the vector.
* Return true when it exists, otherwise false.
*/
bool contains(const T &value) const
{
return this->first_index_of_try(value) != -1;
}
/**
* Get access to the underlying array.
*/
T *data()
{
return begin_;
}
/**
* Get access to the underlying array.
*/
const T *data() const
{
return begin_;
}
T *begin()
{
return begin_;
}
T *end()
{
return end_;
}
const T *begin() const
{
return begin_;
}
const T *end() const
{
return end_;
}
/**
* Get the current capacity of the vector, i.e. the maximum number of elements the vector can
* hold, before it has to reallocate.
*/
uint capacity() const
{
return (uint)(capacity_end_ - begin_);
}
/**
* Get an index range that makes looping over all indices more convenient and less error prone.
* Obviously, this should only be used when you actually need the index in the loop.
*
* Example:
* for (uint i : myvector.index_range()) {
* do_something(i, my_vector[i]);
* }
*/
IndexRange index_range() const
{
return IndexRange(this->size());
}
/**
* Print some debug information about the vector.
*/
void print_stats(StringRef name = "") const
{
std::cout << "Vector Stats: " << name << "\n";
std::cout << " Address: " << this << "\n";
std::cout << " Elements: " << this->size() << "\n";
std::cout << " Capacity: " << (capacity_end_ - begin_) << "\n";
std::cout << " Inline Capacity: " << InlineBufferCapacity << "\n";
char memory_size_str[15];
BLI_str_format_byte_unit(memory_size_str, sizeof(*this), true);
std::cout << " Size on Stack: " << memory_size_str << "\n";
}
private:
T *inline_buffer() const
{
return (T *)inline_buffer_.ptr();
}
bool is_inline() const
{
return begin_ == this->inline_buffer();
}
void ensure_space_for_one()
{
if (UNLIKELY(end_ >= capacity_end_)) {
this->realloc_to_at_least(this->size() + 1);
}
}
2020-07-03 14:52:51 +02:00
BLI_NOINLINE void realloc_to_at_least(const uint min_capacity)
{
if (this->capacity() >= min_capacity) {
return;
}
/* At least double the size of the previous allocation. Otherwise consecutive calls to grow can
* cause a reallocation every time even though min_capacity only increments. */
2020-07-03 14:52:51 +02:00
const uint min_new_capacity = this->capacity() * 2;
2020-07-03 14:52:51 +02:00
const uint new_capacity = std::max(min_capacity, min_new_capacity);
const uint size = this->size();
T *new_array = (T *)allocator_.allocate(new_capacity * (uint)sizeof(T), alignof(T), AT);
uninitialized_relocate_n(begin_, size, new_array);
if (!this->is_inline()) {
allocator_.deallocate(begin_);
}
begin_ = new_array;
end_ = begin_ + size;
capacity_end_ = begin_ + new_capacity;
}
/**
* Initialize all properties, except for allocator_, which has to be initialized beforehand.
*/
template<uint OtherInlineBufferCapacity>
void init_copy_from_other_vector(const Vector<T, OtherInlineBufferCapacity, Allocator> &other)
{
allocator_ = other.allocator_;
2020-07-03 14:52:51 +02:00
const uint size = other.size();
uint capacity;
if (size <= InlineBufferCapacity) {
begin_ = this->inline_buffer();
capacity = InlineBufferCapacity;
}
else {
begin_ = (T *)allocator_.allocate(sizeof(T) * size, alignof(T), AT);
capacity = size;
}
end_ = begin_ + size;
capacity_end_ = begin_ + capacity;
uninitialized_copy_n(other.data(), size, begin_);
UPDATE_VECTOR_SIZE(this);
}
};
#undef UPDATE_VECTOR_SIZE
/**
* Use when the vector is used in the local scope of a function. It has a larger inline storage by
* default to make allocations less likely.
*/
template<typename T, uint InlineBufferCapacity = 20>
using ScopedVector = Vector<T, InlineBufferCapacity, GuardedAllocator>;
} /* namespace blender */
2019-09-13 21:12:26 +10:00
#endif /* __BLI_VECTOR_HH__ */