736 lines
24 KiB
C
736 lines
24 KiB
C
|
#ifndef VTR_SMALL_VECTOR
|
||
|
#define VTR_SMALL_VECTOR
|
||
|
#include <memory>
|
||
|
#include <algorithm>
|
||
|
#include <limits>
|
||
|
#include <cstdint>
|
||
|
#include "vtr_assert.h"
|
||
|
|
||
|
namespace vtr {
|
||
|
|
||
|
namespace small_vector_impl {
|
||
|
|
||
|
//The long format view of the vector, which consists of a dynamically allocated array,
|
||
|
//capacity and size.
|
||
|
template<class T, class S>
|
||
|
struct long_format {
|
||
|
T* data_ = nullptr;
|
||
|
S capacity_ = 0;
|
||
|
S size_ = 0;
|
||
|
};
|
||
|
|
||
|
//The short format view of the vector, which consists of an in-place (potentially empty)
|
||
|
//array of objects, a pad, and a size.
|
||
|
template<class T, class S, size_t CAPACITY, size_t PAD>
|
||
|
struct short_format {
|
||
|
std::array<T, CAPACITY> data_;
|
||
|
std::array<uint8_t, PAD> pad_; //Padding to keep size_ aligned in both long_format and short_format
|
||
|
S size_ = 0;
|
||
|
};
|
||
|
|
||
|
//We need a specialized version of short_format for padding of size zero,
|
||
|
//since a std::array with zero array size may still have non-zero sizeof()
|
||
|
template<class T, class S, size_t CAPACITY>
|
||
|
struct short_format<T, S, CAPACITY, 0> {
|
||
|
std::array<T, CAPACITY> data_;
|
||
|
S size_ = 0;
|
||
|
};
|
||
|
|
||
|
} // namespace small_vector_impl
|
||
|
|
||
|
//vtr::small_vector is a std::vector like container which:
|
||
|
// * consumes less memory: sizeof(vtr::small_vector) < sizeof(std::vector)
|
||
|
// * possibly stores elements in-place (i.e. within the object)
|
||
|
//
|
||
|
//On a typical LP64 system a vtr::small_vector consumes 16 bytes by default and supports
|
||
|
//vectors up to ~2^32 elements long, while a std::vector consumes 24 bytes and supports up
|
||
|
//to ~2^64 elements. The type used to store the size and capacity is configurable,
|
||
|
//and set by the second template parameter argument. Setting it to size_t will replicate
|
||
|
//std::vector's characteristics.
|
||
|
//
|
||
|
//For short vectors vtr::small_vector will try to store elements in-place (i.e. within the
|
||
|
//vtr::small_vector object) instead of dynamically allocating an array (by re-using the
|
||
|
//internal storage for the pointer, size and capacity). Whether this is possible depends on
|
||
|
//the size and alignment requirements of the value type, as compared to
|
||
|
//vtr::small_vector. If in-place storage is not possible (e.g. due to a large value
|
||
|
//type, or a large number of elements) a dynamic buffer is allocated (similar to
|
||
|
//std::vector).
|
||
|
//
|
||
|
//This is a highly specialized container. Unless you have specifically measured it's
|
||
|
//usefulness you should use std::vector.
|
||
|
template<class T, class S = uint32_t>
|
||
|
class small_vector {
|
||
|
public: //Types
|
||
|
typedef T value_type;
|
||
|
//typedef allocator_type //Allocator, unimplemented
|
||
|
typedef value_type& reference;
|
||
|
typedef const value_type& const_reference;
|
||
|
typedef value_type* pointer;
|
||
|
typedef const value_type* const_pointer;
|
||
|
|
||
|
typedef T* iterator;
|
||
|
typedef const T* const_iterator;
|
||
|
typedef std::reverse_iterator<iterator> reverse_iterator;
|
||
|
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
|
||
|
|
||
|
typedef ptrdiff_t difference_type;
|
||
|
typedef S size_type;
|
||
|
|
||
|
public: //Constructors
|
||
|
small_vector() {
|
||
|
if (SHORT_CAPACITY == 0) {
|
||
|
long_.data_ = nullptr;
|
||
|
long_.capacity_ = 0;
|
||
|
}
|
||
|
set_size(0);
|
||
|
}
|
||
|
small_vector(size_type nelem)
|
||
|
: small_vector() {
|
||
|
reserve(nelem);
|
||
|
for (size_type i = 0; i < nelem; i++) {
|
||
|
emplace_back();
|
||
|
}
|
||
|
set_size(0);
|
||
|
}
|
||
|
|
||
|
public: //Accessors
|
||
|
const_iterator begin() const {
|
||
|
return cbegin();
|
||
|
}
|
||
|
|
||
|
const_iterator end() const {
|
||
|
return cend();
|
||
|
}
|
||
|
|
||
|
const_reverse_iterator rbegin() const {
|
||
|
return crbegin();
|
||
|
}
|
||
|
|
||
|
const_reverse_iterator rend() const {
|
||
|
return crend();
|
||
|
}
|
||
|
|
||
|
const_iterator cbegin() const {
|
||
|
if (is_short()) {
|
||
|
return short_.data_.data();
|
||
|
}
|
||
|
return long_.data_;
|
||
|
}
|
||
|
|
||
|
const_iterator cend() const {
|
||
|
if (is_short()) {
|
||
|
return short_.data_.data() + size();
|
||
|
}
|
||
|
return long_.data_ + size();
|
||
|
}
|
||
|
|
||
|
const_reverse_iterator crbegin() const {
|
||
|
return const_reverse_iterator(cend());
|
||
|
}
|
||
|
|
||
|
const_reverse_iterator crend() const {
|
||
|
return const_reverse_iterator(cbegin());
|
||
|
}
|
||
|
|
||
|
size_type size() const {
|
||
|
//Padding ensures long/short format sizes are always aligned
|
||
|
return long_.size_;
|
||
|
}
|
||
|
|
||
|
size_t max_size() const {
|
||
|
return std::numeric_limits<S>::max();
|
||
|
}
|
||
|
|
||
|
size_type capacity() const {
|
||
|
if (is_short()) {
|
||
|
return SHORT_CAPACITY; //Fixed capacity
|
||
|
}
|
||
|
return long_.capacity_;
|
||
|
}
|
||
|
|
||
|
bool empty() const { return size() == 0; }
|
||
|
|
||
|
const_reference operator[](size_t i) const {
|
||
|
if (is_short()) {
|
||
|
return short_.data_[i];
|
||
|
}
|
||
|
return long_.data_[i];
|
||
|
}
|
||
|
|
||
|
const_reference at(size_t i) const {
|
||
|
if (i > size()) {
|
||
|
throw std::out_of_range("Index out of bounds");
|
||
|
}
|
||
|
return operator[](i);
|
||
|
}
|
||
|
|
||
|
const_reference front() const {
|
||
|
return *begin();
|
||
|
}
|
||
|
|
||
|
const_reference back() const {
|
||
|
return *(end() - 1);
|
||
|
}
|
||
|
|
||
|
const_pointer data() const {
|
||
|
if (is_short()) {
|
||
|
short_.data_;
|
||
|
}
|
||
|
return long_.data_;
|
||
|
}
|
||
|
|
||
|
public: //Mutators
|
||
|
iterator begin() {
|
||
|
//Call const method and cast-away constness
|
||
|
return const_cast<iterator>(const_cast<const small_vector<T, S>*>(this)->begin());
|
||
|
}
|
||
|
|
||
|
iterator end() {
|
||
|
return const_cast<iterator>(const_cast<const small_vector<T, S>*>(this)->end());
|
||
|
}
|
||
|
|
||
|
reverse_iterator rbegin() {
|
||
|
//Call const method and cast-away constness
|
||
|
return reverse_iterator(const_cast<small_vector<T, S>*>(this)->end());
|
||
|
}
|
||
|
|
||
|
reverse_iterator rend() {
|
||
|
return reverse_iterator(const_cast<small_vector<T, S>*>(this)->begin());
|
||
|
}
|
||
|
|
||
|
void resize(size_type n) {
|
||
|
resize(n, value_type());
|
||
|
}
|
||
|
|
||
|
void resize(size_type n, value_type val) {
|
||
|
if (n < size()) {
|
||
|
//Remove at end
|
||
|
erase(begin() + n, end());
|
||
|
} else if (n > size()) {
|
||
|
//Insert new elements at end
|
||
|
insert(end(), n - size(), val);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void reserve(size_type num_elems) {
|
||
|
//Don't change capacity unless requested number of elements is both:
|
||
|
// * More than the short capacity (no need to reserve up to short capacity)
|
||
|
// * Greater than the current size (capacity can never be below size)
|
||
|
if (num_elems > SHORT_CAPACITY && num_elems > size()) {
|
||
|
change_capacity(num_elems);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void shrink_to_fit() {
|
||
|
if (!is_short()) {
|
||
|
change_capacity(size());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
reference operator[](size_t i) {
|
||
|
return const_cast<reference>(const_cast<const small_vector<T, S>*>(this)->operator[](i));
|
||
|
}
|
||
|
|
||
|
reference at(size_t i) {
|
||
|
return const_cast<reference>(const_cast<const small_vector<T, S>*>(this)->at(i));
|
||
|
}
|
||
|
|
||
|
reference front() {
|
||
|
return const_cast<reference>(const_cast<const small_vector<T, S>*>(this)->front());
|
||
|
}
|
||
|
|
||
|
reference back() {
|
||
|
return const_cast<reference>(const_cast<const small_vector<T, S>*>(this)->back());
|
||
|
}
|
||
|
|
||
|
pointer data() {
|
||
|
return const_cast<pointer>(const_cast<const small_vector<T, S>*>(this)->data());
|
||
|
}
|
||
|
|
||
|
template<class InputIterator>
|
||
|
void assign(InputIterator first, InputIterator last) {
|
||
|
insert(begin(), first, last);
|
||
|
}
|
||
|
|
||
|
void assign(size_type n, const value_type& val) {
|
||
|
insert(begin(), n, val);
|
||
|
}
|
||
|
|
||
|
void assign(std::initializer_list<value_type> il) {
|
||
|
assign(il.begin(), il.end());
|
||
|
}
|
||
|
|
||
|
void push_back(value_type value) {
|
||
|
//Construct default value_type at new location
|
||
|
auto new_ptr = next_back();
|
||
|
|
||
|
new (new_ptr) T();
|
||
|
|
||
|
//Since we took a copy in the argument, we can move it
|
||
|
//into the new location
|
||
|
*new_ptr = std::move(value);
|
||
|
}
|
||
|
|
||
|
void pop_back() {
|
||
|
if (size() > 0) {
|
||
|
erase(end() - 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
iterator insert(const_iterator position, const value_type& val) {
|
||
|
return insert(position, 1, val);
|
||
|
}
|
||
|
|
||
|
iterator insert(const_iterator position, size_type n, const value_type& val) {
|
||
|
//Location of position as an index, which will be
|
||
|
//unchanged if the underlying storage is reallocated
|
||
|
size_type i = std::distance(cbegin(), position);
|
||
|
|
||
|
//If needed, grow capacity
|
||
|
//
|
||
|
//Note that change_capacity will automatically convert from short to long
|
||
|
//format if required.
|
||
|
size_type new_size = size() + n;
|
||
|
if (capacity() < new_size) {
|
||
|
change_capacity(new_size);
|
||
|
}
|
||
|
|
||
|
iterator first = begin() + i;
|
||
|
iterator last = first + n;
|
||
|
reverse_swap_elements(first, end(), end() + n - 1);
|
||
|
|
||
|
//Insert new values at end
|
||
|
std::uninitialized_fill(first, last, val);
|
||
|
|
||
|
set_size(new_size);
|
||
|
|
||
|
return first;
|
||
|
}
|
||
|
|
||
|
iterator insert(const_iterator position, size_type n, value_type&& val) {
|
||
|
return insert(position, n, value_type(val)); //TODO: optimize for moved val
|
||
|
}
|
||
|
|
||
|
//Range insert
|
||
|
//template<class InputIterator>
|
||
|
//iterator insert(const_iterator position, InputIterator first, InputIterator last) {
|
||
|
////Location of position as an index, which will be
|
||
|
////unchanged if the underlying storage is reallocated
|
||
|
//size_type i = std::distance(cbegin(), position);
|
||
|
//size_type n = std::distance(first, last);
|
||
|
|
||
|
////If needed, grow capacity
|
||
|
////
|
||
|
////Note that change_capacity will automatically convert from short to long
|
||
|
////format if required.
|
||
|
//size_type new_size = size() + n;
|
||
|
//if (capacity() < new_size) {
|
||
|
//change_capacity(new_size);
|
||
|
//}
|
||
|
|
||
|
//reverse_swap_elements(begin() + i, end(), end() + n - 1);
|
||
|
|
||
|
////Insert new values at end
|
||
|
//std::uninitialized_copy(first, last, begin() + i);
|
||
|
|
||
|
//set_size(new_size);
|
||
|
|
||
|
//return begin() + i;
|
||
|
//}
|
||
|
|
||
|
iterator erase(const_iterator position) {
|
||
|
return erase(position, position + 1);
|
||
|
}
|
||
|
|
||
|
iterator erase(const_iterator first, const_iterator last) {
|
||
|
//Number of elements to erase
|
||
|
size_type n = std::distance(first, last);
|
||
|
|
||
|
//Location of position as an index, which will be
|
||
|
//unchanged if the underlying storage is changed
|
||
|
size_type i_first = std::distance(cbegin(), first);
|
||
|
|
||
|
size_type new_size = size() - n;
|
||
|
|
||
|
const_iterator position = first;
|
||
|
|
||
|
if (!is_short() && new_size <= SHORT_CAPACITY) {
|
||
|
//Convert from long format to short/in-place format
|
||
|
|
||
|
//Keep handle on buffer and original size
|
||
|
auto buff_ptr = long_.data_;
|
||
|
size_type orig_size = size();
|
||
|
|
||
|
//Copy into in-place the valid (not-to-be-erased) values in
|
||
|
//[begin, first) and [last, end)
|
||
|
//
|
||
|
//Note that we can use uninitialized_copy since the long format
|
||
|
//has only basic data types, which have no destructors to call
|
||
|
auto buff_begin = buff_ptr;
|
||
|
auto buff_end = buff_begin + orig_size;
|
||
|
auto erase_begin = buff_ptr + i_first;
|
||
|
auto erase_end = erase_begin + n;
|
||
|
|
||
|
//Copy from beginning until start of erase
|
||
|
auto inplace_ptr = short_.data_.data();
|
||
|
for (auto buff_itr = buff_begin; buff_itr != erase_begin; ++buff_itr) {
|
||
|
new (inplace_ptr++) T(*buff_itr);
|
||
|
}
|
||
|
//Copy from end of erase until end of buf
|
||
|
for (auto buff_itr = erase_end; buff_itr != buff_end; ++buff_itr) {
|
||
|
new (inplace_ptr++) T(*buff_itr);
|
||
|
}
|
||
|
|
||
|
VTR_ASSERT_SAFE(std::distance(short_.data_.data(), inplace_ptr) == new_size);
|
||
|
|
||
|
//Clean-up elements in buffer and free it
|
||
|
destruct_elements(buff_begin, buff_end);
|
||
|
dealloc(buff_ptr);
|
||
|
|
||
|
//New position
|
||
|
position = begin() + i_first;
|
||
|
} else {
|
||
|
//Remove elements in either long or short formats
|
||
|
|
||
|
iterator first2 = begin() + i_first;
|
||
|
iterator last2 = first2 + n;
|
||
|
|
||
|
//Swap all elements in [first, last) to the end.
|
||
|
//That is with those within [last, end())
|
||
|
if (last2 < end()) {
|
||
|
swap_elements(last2, end(), first2);
|
||
|
}
|
||
|
|
||
|
//Finally destruct the elements in [last, end()); that is the
|
||
|
//elements which were originally to be erased
|
||
|
destruct_elements(end() - n, end());
|
||
|
|
||
|
//Note that capacity is unchanged, so we do not need to change
|
||
|
//position in this case
|
||
|
}
|
||
|
|
||
|
//Shrink size
|
||
|
set_size(new_size);
|
||
|
|
||
|
return begin() + std::distance(cbegin(), position);
|
||
|
}
|
||
|
|
||
|
void swap(small_vector<T, S>& other) {
|
||
|
swap(*this, other);
|
||
|
}
|
||
|
|
||
|
friend void swap(small_vector<T, S>& lhs, small_vector<T, S>& rhs) {
|
||
|
using std::swap;
|
||
|
|
||
|
if (lhs.is_short() && rhs.is_short()) {
|
||
|
//Both short
|
||
|
std::swap(lhs.short_, rhs.short_);
|
||
|
} else if (!lhs.is_short() && !rhs.is_short()) {
|
||
|
//Both long
|
||
|
std::swap(lhs.long_, rhs.long_);
|
||
|
} else {
|
||
|
//Mixed long/short
|
||
|
VTR_ASSERT_SAFE(lhs.is_short() != rhs.is_short());
|
||
|
|
||
|
auto& long_vec = ((lhs.is_short()) ? rhs : lhs);
|
||
|
auto& short_vec = ((lhs.is_short()) ? lhs : rhs);
|
||
|
|
||
|
//If the two vectors are in different formats we can't just swap them,
|
||
|
//since the short format has real values (potentially with destructors),
|
||
|
//while the long format has only basic data types.
|
||
|
//
|
||
|
//Instead we copy the short_vec values into long, destruct the original short_vec
|
||
|
//values and then set short_vec to point to long_vec's original buffer (avoids
|
||
|
//extra copy of long elements).
|
||
|
|
||
|
//Save long data
|
||
|
pointer long_buf = long_vec.long_.data_;
|
||
|
size_type long_size = long_vec.long_size_;
|
||
|
size_type long_capacity = long_vec.long_.capacity_;
|
||
|
|
||
|
//Copy short data into long
|
||
|
//
|
||
|
//Note that the long format contains only basic data types with no destructors to call,
|
||
|
//so we can use uninitialzed copy
|
||
|
std::uninitialized_copy(short_vec.short_.begin(), short_vec.short_.end(), long_vec.short_.data_);
|
||
|
long_vec.short_.size_ = short_vec.size();
|
||
|
|
||
|
//Destroy original elements in short
|
||
|
short_vec.destruct_elements();
|
||
|
|
||
|
//Copy long data into short
|
||
|
short_vec.long_.data = long_buf;
|
||
|
short_vec.long_.capacity_ = long_capacity;
|
||
|
short_vec.long_.size_ = long_size;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void clear() {
|
||
|
//Destruct all elements and clear size, but do not free memory
|
||
|
destruct_elements();
|
||
|
set_size(0);
|
||
|
}
|
||
|
|
||
|
template<typename... Args>
|
||
|
void emplace_back(Args&&... args) {
|
||
|
//Construct in-place
|
||
|
new (next_back()) T(std::forward<Args>(args)...);
|
||
|
}
|
||
|
|
||
|
//Unsupported: Emplace at position
|
||
|
//template<typename... Args>
|
||
|
//void emplace(const_iterator position, Args&&... args) {
|
||
|
//throw std::logic_error("unimplemented");
|
||
|
//}
|
||
|
|
||
|
public: //Comparisons
|
||
|
friend bool operator==(const small_vector<T, S>& lhs, const small_vector<T, S>& rhs) {
|
||
|
if (lhs.size() != rhs.size()) {
|
||
|
return false;
|
||
|
}
|
||
|
return std::equal(lhs.begin(), lhs.end(),
|
||
|
rhs.begin());
|
||
|
}
|
||
|
|
||
|
friend bool operator<(const small_vector<T, S>& lhs, const small_vector<T, S>& rhs) {
|
||
|
return std::lexicographical_compare(lhs.begin(), lhs.end(),
|
||
|
rhs.begin(), rhs.end());
|
||
|
}
|
||
|
|
||
|
friend bool operator!=(const small_vector<T, S>& lhs, const small_vector<T, S>& rhs) {
|
||
|
return !(lhs == rhs);
|
||
|
}
|
||
|
|
||
|
friend bool operator>(const small_vector<T, S>& lhs, const small_vector<T, S>& rhs) {
|
||
|
return rhs < lhs;
|
||
|
}
|
||
|
|
||
|
friend bool operator<=(const small_vector<T, S>& lhs, const small_vector<T, S>& rhs) {
|
||
|
return !(rhs < lhs);
|
||
|
}
|
||
|
|
||
|
friend bool operator>=(const small_vector<T, S>& lhs, const small_vector<T, S>& rhs) {
|
||
|
return !(lhs < rhs);
|
||
|
}
|
||
|
|
||
|
public: //Lifetime management
|
||
|
~small_vector() {
|
||
|
destruct_elements();
|
||
|
if (!is_short()) {
|
||
|
dealloc(long_.data_);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
small_vector(const small_vector& other) {
|
||
|
if (other.is_short()) {
|
||
|
~small_vector(); //Clean-up elements
|
||
|
|
||
|
//Copy in place
|
||
|
short_ = other.short_;
|
||
|
} else {
|
||
|
if (!is_short() && capacity() >= other.size()) {
|
||
|
//Re-use existing buffer, since it has sufficient capacity
|
||
|
destruct_elements();
|
||
|
|
||
|
} else {
|
||
|
~small_vector(); //Clean-up elements, potentially freeing buffer
|
||
|
|
||
|
//Create new buffer of exact size
|
||
|
long_.data_ = alloc(other.size());
|
||
|
long_.capacity_ = other.size();
|
||
|
}
|
||
|
|
||
|
set_size(other.size());
|
||
|
|
||
|
//Copy elements
|
||
|
std::uninitialized_copy(other.begin(), other.end(), long_.data_);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
small_vector(small_vector&& other)
|
||
|
: small_vector() {
|
||
|
swap(*this, other); //Copy-swap
|
||
|
}
|
||
|
|
||
|
small_vector& operator=(small_vector other) {
|
||
|
swap(*this, other); //Copy-swap
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
private: //Internal types
|
||
|
static constexpr size_t LONG_FMT_SIZE = sizeof(small_vector_impl::long_format<value_type, size_type>);
|
||
|
static constexpr size_t LONG_FMT_ALIGN = alignof(small_vector_impl::long_format<value_type, size_type>);
|
||
|
|
||
|
//The number of value types which can be stored in-place in the object (may be zero)
|
||
|
static constexpr size_t SHORT_CAPACITY = (LONG_FMT_SIZE - sizeof(size_type)) / sizeof(value_type);
|
||
|
|
||
|
//The amount of padding required to ensure the size_ attributes of long_format and short_format
|
||
|
//are aligned.
|
||
|
static constexpr size_t SHORT_PAD = LONG_FMT_SIZE - (sizeof(value_type) * SHORT_CAPACITY + sizeof(size_type));
|
||
|
|
||
|
static constexpr size_t SHORT_FMT_SIZE = sizeof(small_vector_impl::short_format<value_type, size_type, SHORT_CAPACITY, SHORT_PAD>);
|
||
|
static constexpr size_t SHORT_FMT_ALIGN = alignof(small_vector_impl::short_format<value_type, size_type, SHORT_CAPACITY, SHORT_PAD>);
|
||
|
|
||
|
static_assert(LONG_FMT_SIZE == SHORT_FMT_SIZE, "Short and long data formats must have same size");
|
||
|
static_assert(LONG_FMT_ALIGN % SHORT_FMT_ALIGN == 0, "Short and long data formats must have compatible alignment");
|
||
|
|
||
|
public:
|
||
|
static constexpr size_t INPLACE_CAPACITY = SHORT_CAPACITY;
|
||
|
|
||
|
private: //Internal methods
|
||
|
//Returns a pointer to the (uninitialized) location for the next element to be added.
|
||
|
//Automatically grows the storage if needed.
|
||
|
T* next_back() {
|
||
|
T* next = nullptr;
|
||
|
if (size() < SHORT_CAPACITY) { //Space in-place
|
||
|
next = short_.data_.data() + size();
|
||
|
} else { //Dynamically allocated
|
||
|
if (size() == capacity()) {
|
||
|
//Out of space
|
||
|
grow();
|
||
|
}
|
||
|
next = long_.data_ + size();
|
||
|
}
|
||
|
++long_.size_;
|
||
|
VTR_ASSERT_SAFE(size() <= capacity());
|
||
|
return next;
|
||
|
}
|
||
|
|
||
|
//Increases the capacity by GROWTH_FACTOR
|
||
|
//
|
||
|
//Note that this automatically handles the case of growing beyond SHORT_CAPACITY and
|
||
|
//switching to long_format
|
||
|
void grow() {
|
||
|
//How much to scale the size of the storage when out of space
|
||
|
constexpr size_type GROWTH_FACTOR = 2;
|
||
|
|
||
|
VTR_ASSERT_SAFE_MSG(size() >= SHORT_CAPACITY, "Should only grow capacity when at or beyond SHORT_CAPACITY");
|
||
|
VTR_ASSERT_SAFE_MSG(capacity() <= (max_size() / GROWTH_FACTOR), "No capacity overflow");
|
||
|
size_type new_capacity = std::max<size_type>(1, capacity() * GROWTH_FACTOR);
|
||
|
//TODO: Consider ensuring new_capacity is always a power of 2, may be easier on the memory allocator...
|
||
|
|
||
|
VTR_ASSERT_SAFE_MSG(new_capacity > capacity(), "Grown capacity should be greater than previous capacity");
|
||
|
|
||
|
change_capacity(new_capacity);
|
||
|
}
|
||
|
|
||
|
//Changes capacity to new_capacity
|
||
|
//
|
||
|
//It is assumed that new_capacity is > SHORT_CAPACITY.
|
||
|
//
|
||
|
//If currently in short format, automatically converts to long format
|
||
|
void change_capacity(size_type new_capacity) {
|
||
|
VTR_ASSERT_SAFE_MSG(new_capacity >= size(), "New capacity should be at least size");
|
||
|
|
||
|
if (new_capacity == capacity()) {
|
||
|
return; //Already at correct capacity
|
||
|
}
|
||
|
|
||
|
//Get new raw memory
|
||
|
T* tmp_data = alloc(new_capacity);
|
||
|
|
||
|
//Copy values
|
||
|
std::uninitialized_copy(begin(), end(), tmp_data);
|
||
|
|
||
|
//Clean-up the old values
|
||
|
//We do this before updating the array pointer, since if we are updating
|
||
|
//from short to long the assignment would corrupt the old values
|
||
|
destruct_elements();
|
||
|
|
||
|
//Update
|
||
|
std::swap(long_.data_, tmp_data);
|
||
|
long_.capacity_ = new_capacity;
|
||
|
|
||
|
//Free memory if we aren't using the inplace buffer
|
||
|
if (!is_short()) {
|
||
|
dealloc(tmp_data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Returns true if using the short/in-place format
|
||
|
bool is_short() const {
|
||
|
return SHORT_CAPACITY > 0u //Can use the inplace buffer
|
||
|
&& size() <= SHORT_CAPACITY; //Not using the dynamic buffer
|
||
|
}
|
||
|
|
||
|
void set_size(size_type new_size) {
|
||
|
//The two data (short/long) are padded to
|
||
|
//ensure that thier size_ members area always
|
||
|
//aligned, allowing is to set the size directly
|
||
|
//for both formats
|
||
|
short_.size_ = new_size;
|
||
|
}
|
||
|
|
||
|
//Allocates raw (un-initialzied) memory for nelem objects of type T
|
||
|
static T* alloc(size_type nelem) {
|
||
|
return static_cast<T*>(::operator new(sizeof(T) * nelem));
|
||
|
}
|
||
|
|
||
|
//Deallocates a block of memory
|
||
|
//
|
||
|
//Caller must ensure any object's associated with this block have already had
|
||
|
//their destructors called
|
||
|
static void dealloc(T* data) {
|
||
|
::operator delete(data);
|
||
|
}
|
||
|
|
||
|
//Swaps the elements in [src_first, src_last) to positions starting at dst_first
|
||
|
//
|
||
|
//Returns an iterator to the element in the first swapped location
|
||
|
iterator swap_elements(iterator src_first, iterator src_last, iterator dst_first) {
|
||
|
VTR_ASSERT_SAFE_MSG(src_first < src_last, "First swap range first must start before last");
|
||
|
|
||
|
auto dst_itr = dst_first;
|
||
|
for (auto src_itr = src_first; src_itr != src_last; ++src_itr) {
|
||
|
std::swap(*src_itr, *(dst_itr++));
|
||
|
}
|
||
|
|
||
|
return src_first;
|
||
|
}
|
||
|
|
||
|
//Swaps the elements in [src_first, src_last) in reverse order starting at dst_first and working backwards
|
||
|
//
|
||
|
//Returns an iterator to the element in the first swapped location
|
||
|
iterator reverse_swap_elements(iterator src_first, iterator src_last, iterator dst_first) {
|
||
|
VTR_ASSERT_SAFE_MSG(src_first < src_last, "First swap range first must start before last");
|
||
|
|
||
|
auto dst_itr = dst_first;
|
||
|
for (auto src_itr = src_last - 1; src_itr != src_first - 1; --src_itr) {
|
||
|
std::swap(*src_itr, *(dst_itr--));
|
||
|
}
|
||
|
|
||
|
return src_first;
|
||
|
}
|
||
|
|
||
|
//Calls the destructors of all elements currently held
|
||
|
void destruct_elements() {
|
||
|
destruct_elements(begin(), end());
|
||
|
}
|
||
|
|
||
|
void destruct_elements(iterator first, iterator last) {
|
||
|
for (auto itr = first; itr != last; ++itr) {
|
||
|
itr->~T();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void destruct_element(iterator position) {
|
||
|
destruct_elements(position, position + 1);
|
||
|
}
|
||
|
|
||
|
private: //Data
|
||
|
//The object data storage is re-used between the long and short formats.
|
||
|
//
|
||
|
//If the capacity is small (less than or equal to SHORT_CAPACITY) the
|
||
|
//short format (which stores element in-place) is used. Otherwise the
|
||
|
//long format is used and the elements are stored in a dynamically
|
||
|
//allocated buffer
|
||
|
union {
|
||
|
small_vector_impl::long_format<value_type, size_type> long_;
|
||
|
small_vector_impl::short_format<value_type, size_type, SHORT_CAPACITY, SHORT_PAD> short_;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
} // namespace vtr
|
||
|
|
||
|
#endif
|