Home | History | Annotate | Download | only in containers
      1 // Copyright 2016 The Android Open Source Project
      2 //
      3 // This software is licensed under the terms of the GNU General Public
      4 // License version 2, as published by the Free Software Foundation, and
      5 // may be copied, distributed, and modified under those terms.
      6 //
      7 // This program is distributed in the hope that it will be useful,
      8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
      9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     10 // GNU General Public License for more details.
     11 
     12 #pragma once
     13 
     14 #include "android/base/TypeTraits.h"
     15 
     16 #include <algorithm>
     17 #include <initializer_list>
     18 #include <memory>
     19 #include <type_traits>
     20 #include <utility>
     21 
     22 #include <stddef.h>
     23 #include <stdlib.h>
     24 
     25 //
     26 // SmallVector<T>, SmallFixedVector<T, SmallSize>
     27 //
     28 // This header defines a replacement for a std::vector<> that uses small buffer
     29 // optimization technique - for some preset number of elements |SmallSize| it
     30 // stores them inside of the object, and falls back to the dynamically allocated
     31 // array only if one needs to add more elements.
     32 // This is useful for the performance-critical places where common number of
     33 // processed items is small, but it may still be quite large for a stack array.
     34 //
     35 // SmallFixedVector<> is the class you use to store elements, while
     36 // SmallVector<> is its base class that erases the small size from the type.
     37 //
     38 // NOTE: SmallVector<> cannot guarantee std::vector<>'s iterator invalidation
     39 //   rules for move() and swap() operations - std::vector<>s just exchange
     40 //   their iterators on swap() and pass the moved ones over, while SmallVector<>
     41 //   may leave the iterators pointing to nowhere if they were for the in-place
     42 //   array storage.
     43 //
     44 // Currenly only a limited subset of std::vector<>'s operations is implemented,
     45 // but fill free to add the ones you need.
     46 //
     47 
     48 namespace android {
     49 namespace base {
     50 
     51 //
     52 // Forward-declare the 'real' small vector class.
     53 template <class T, size_t S>
     54 class SmallFixedVector;
     55 
     56 //
     57 // SmallVector<T> - an interface for a small-buffer-optimized vector.
     58 // It hides the fixed size from its type, so one can use it to pass small
     59 // vectors around and not leak the buffer size to all callers:
     60 //
     61 //  void process(SmallVector<Foo>& data);
     62 //  ...
     63 //  ...
     64 //  SmallFixedVector<Foo, 100> aLittleBitOfFoos = ...;
     65 //  process(aLittleBitOfFoos);
     66 //  ...
     67 //  SmallFixedVector<Foo, 1000> moreFoos = ...;
     68 //  process(moreFoos);
     69 //
     70 template <class T>
     71 class SmallVector {
     72     // Make them friends so SmallFixedVector is able to refer to SmallVector's
     73     // protected members in static_assert()s.
     74     template <class U, size_t S>
     75     friend class SmallFixedVector;
     76 
     77 public:
     78     // Common set of type aliases.
     79     using value_type = T;
     80     using iterator = T*;
     81     using const_iterator = const T*;
     82     using pointer = T*;
     83     using const_pointer = const T*;
     84     using reference = T&;
     85     using const_reference = const T&;
     86     using size_type = size_t;
     87 
     88     // It's ok to delete SmallVector<> through the base class - dtor() actually
     89     // takes care of all living elements and the allocated memory.
     90     ~SmallVector() { dtor(); }
     91 
     92     // std::vector<> interface operations.
     93     iterator begin() { return mBegin; }
     94     const_iterator begin() const { return mBegin; }
     95     const_iterator cbegin() const { return mBegin; }
     96 
     97     iterator end() { return mEnd; }
     98     const_iterator end() const { return mEnd; }
     99     const_iterator cend() const { return mEnd; }
    100 
    101     size_type size() const { return end() - begin(); }
    102     size_type capacity() const { return mCapacity; }
    103     bool empty() const { return begin() == end(); }
    104 
    105     reference front() { return *begin(); }
    106     const_reference front() const { return *cbegin(); }
    107     reference back() { return *(end() - 1); }
    108     const_reference back() const { return *(cend() - 1); }
    109 
    110     reference operator[](size_t i) { return *(begin() + i); }
    111     const_reference operator[](size_t i) const { return *(cbegin() + i); }
    112 
    113     pointer data() { return mBegin; }
    114     const_pointer data() const { return mBegin; }
    115     const_pointer cdata() const { return mBegin; }
    116 
    117     template <class... Args>
    118     void emplace_back(Args&&... args) {
    119         grow_for_size(size() + 1);
    120         new (mEnd) T(std::forward<Args>(args)...);
    121         ++mEnd;
    122     }
    123 
    124     void push_back(const T& t) { emplace_back(t); }
    125     void push_back(T&& t) { emplace_back(std::move(t)); }
    126 
    127     void pop_back() {
    128         destruct(mEnd - 1, mEnd);
    129         --mEnd;
    130     }
    131 
    132     void clear() {
    133         destruct(begin(), end());
    134         mEnd = mBegin;
    135     }
    136 
    137     void reserve(size_type newCap) {
    138         if (newCap <= this->capacity()) {
    139             return;
    140         }
    141         set_capacity(newCap);
    142     }
    143 
    144     void resize(size_type newSize) { resize_impl<true>(newSize); }
    145 
    146     // This version of resizing doesn't initialize the newly allocated elements
    147     // Useful for the cases when value-initialization is noticeably slow and
    148     // one wants to directly construct or memcpy the elements into the resized
    149     // place.
    150     void resize_noinit(size_type newSize) { resize_impl<false>(newSize); }
    151 
    152     // Returns if the current vector's buffer is dynamically allocated.
    153     bool isAllocated() const { return this->cbegin() != smallBufferStart(); }
    154 
    155 protected:
    156     // Hide the default constructor so only SmallFixedVector can be
    157     // instantiated.
    158     SmallVector() = default;
    159 
    160     // Destroy all elements in the vector and free the array if it was allocated
    161     // dynamically.
    162     void dtor() {
    163         this->destruct(this->begin(), this->end());
    164         if (isAllocated()) {
    165             free(this->mBegin);
    166         }
    167     }
    168 
    169     // Just a convenience setter function to init all members at once.
    170     void init(iterator begin, iterator end, size_type capacity) {
    171         this->mBegin = begin;
    172         this->mEnd = end;
    173         this->mCapacity = capacity;
    174     }
    175 
    176     // An implementation of different resizing versions.
    177     template <bool init>
    178     void resize_impl(size_type newSize) {
    179         if (newSize < this->size()) {
    180             const auto newEnd = this->begin() + newSize;
    181             this->destruct(newEnd, this->end());
    182             this->mEnd = newEnd;
    183         } else if (newSize > this->size()) {
    184             grow_for_size(newSize);
    185             const auto newEnd = this->begin() + newSize;
    186             if (init) {
    187                 std::uninitialized_fill(this->end(), newEnd, T());
    188             }
    189             this->mEnd = newEnd;
    190         }
    191     }
    192 
    193     // Templated append operation for a range of elements.
    194     template <class Iter>
    195     void insert_back(Iter b, Iter e) {
    196         if (b == e) {
    197             return;
    198         }
    199         const auto newSize = this->size() + (e - b);
    200         grow_for_size(newSize);
    201         this->mEnd = std::uninitialized_copy(b, e, this->mEnd);
    202     }
    203 
    204     // Multiplicative grow for the internal array so it can hold |newSize|
    205     // elements.
    206     // Doesn't change size(), only capacity().
    207     void grow_for_size(size_type newSize) {
    208         // Grow by 1.5x by default.
    209         if (newSize > capacity()) {
    210             set_capacity(std::max(newSize, capacity() + capacity() / 2));
    211         }
    212     }
    213 
    214     // Sets the capacity() to be exacly |newCap|. Allocates the array
    215     // dynamically, moves all elements over and (potentially) deallocates the
    216     // old array.
    217     // Doesn't change size(), only capacity().
    218     void set_capacity(size_type newCap) {
    219         // Here we can only be switching to the dynamic vector, as static one
    220         // always has its capacity on the maximum.
    221         const auto newBegin = (T*)malloc(sizeof(T) * newCap);
    222         if (!newBegin) {
    223             abort();  // what else can we do here?
    224         }
    225         const auto newEnd = std::uninitialized_copy(
    226                 std::make_move_iterator(this->begin()),
    227                 std::make_move_iterator(this->end()), newBegin);
    228         dtor();
    229         this->mBegin = newBegin;
    230         this->mEnd = newEnd;
    231         this->mCapacity = newCap;
    232     }
    233 
    234     // A convenience function to call destructor for a range of elements.
    235     static void destruct(T* b, T* e) {
    236         if (!std::is_trivially_destructible<T>::value) {
    237             for (; b != e; ++b) {
    238                 b->~T();
    239             }
    240         }
    241     }
    242 
    243     // By design of the class, SmallFixedVector<> will be inheriting from
    244     // SmallVector<>, so its in-place storage array is going to be the very next
    245     // member after the last one here.
    246     // This function returns that address, and SmallFixedVector<> has a static
    247     // assert to make sure it remains correct.
    248     constexpr const void* smallBufferStart() const {
    249         return (const void*)(&mCapacity + 1);
    250     }
    251 
    252     // Standard set of members for a vector - begin, end and capacity.
    253     // These point to the currently used chunk of memory, no matter if it's a
    254     // heap-allocated one or an in-place array.
    255     iterator mBegin;
    256     iterator mEnd;
    257     size_type mCapacity;
    258 };
    259 
    260 // The implementation of a SmallVector with a fixed in-place size, |SmallSize|.
    261 template <class T, size_t SmallSize>
    262 class SmallFixedVector : public SmallVector<T> {
    263     using base = SmallVector<T>;
    264 
    265 public:
    266     // Grab these from the base class.
    267     using value_type = typename base::value_type;
    268     using iterator = typename base::iterator;
    269     using const_iterator = typename base::const_iterator;
    270     using pointer = typename base::pointer;
    271     using const_pointer = typename base::const_pointer;
    272     using reference = typename base::reference;
    273     using const_reference = typename base::const_reference;
    274     using size_type = typename base::size_type;
    275 
    276     static constexpr size_type kSmallSize = SmallSize;
    277 
    278     // Default constructor - set up an empty vector with capacity at full
    279     // internal array size.
    280     SmallFixedVector() {
    281         // Make sure that the small array starts exactly where base class
    282         // expects it: right after the |mCapacity|.
    283 
    284         // We can't use a static_assert with offsetof() because in msvc, it uses
    285         // reinterpret_cast.
    286         // TODO: Add runtime assertion instead?
    287         // https://developercommunity.visualstudio.com/content/problem/22196/static-assert-cannot-compile-constexprs-method-tha.html
    288 #ifndef _MSC_VER
    289         static_assert(offsetof(base, mCapacity) + sizeof(base::mCapacity) ==
    290                                       offsetof(SmallFixedVector, mData) &&
    291                               offsetof(Data, array) == 0,
    292                       "SmallFixedVector<> class layout is wrong, "
    293                       "|mData| needs to follow |mCapacity|");
    294 #endif
    295 
    296         init_inplace();
    297     }
    298 
    299     // Ctor from a range of iterators
    300     template <class Iter>
    301     SmallFixedVector(Iter b, Iter e) : SmallFixedVector() {
    302         this->insert_back(b, e);
    303     }
    304 
    305     // Ctor from a range - anything that has begin and end.
    306     // Note: template constructor is never a copy/move-ctor.
    307     template <class Range,
    308               class = enable_if_c<!std::is_same<Range, T>::value &&
    309                                   is_range<Range>::value>>
    310     explicit SmallFixedVector(const Range& r)
    311         : SmallFixedVector(std::begin(r), std::end(r)) {}
    312     template <class Range,
    313               class = enable_if_c<!std::is_same<Range, T>::value &&
    314                                   is_range<Range>::value>>
    315     explicit SmallFixedVector(Range&& r)
    316         : SmallFixedVector(std::make_move_iterator(std::begin(r)),
    317                            std::make_move_iterator(std::end(r))) {}
    318     template <class U, class = enable_if_convertible<U, T>>
    319     SmallFixedVector(std::initializer_list<U> list)
    320         : SmallFixedVector(std::begin(list), std::end(list)) {}
    321 
    322     SmallFixedVector(const SmallFixedVector& other)
    323         : SmallFixedVector(other.begin(), other.end()) {}
    324 
    325     SmallFixedVector(SmallFixedVector&& other) {
    326         if (other.isAllocated()) {
    327             // Just steal the allocated memory from the |other|.
    328             this->mBegin = other.mBegin;
    329             this->mEnd = other.mEnd;
    330             this->mCapacity = other.mCapacity;
    331             other.init_inplace();
    332         } else {
    333             // Have to move individual elements.
    334             this->mBegin = mData.array;
    335             this->mEnd = std::uninitialized_copy(
    336                     std::make_move_iterator(other.begin()),
    337                     std::make_move_iterator(other.end()), this->begin());
    338             this->mCapacity = kSmallSize;
    339         }
    340     }
    341 
    342     SmallFixedVector& operator=(const SmallFixedVector& other) {
    343         if (&other != this) {
    344             this->clear();
    345             this->insert_back(other.begin(), other.end());
    346         }
    347         return *this;
    348     }
    349 
    350     SmallFixedVector& operator=(SmallFixedVector&& other) {
    351         if (other.isAllocated()) {
    352             // Steal it and we're done.
    353             this->dtor();
    354             this->mBegin = other.mBegin;
    355             this->mEnd = other.mEnd;
    356             this->mCapacity = other.mCapacity;
    357             other.init_inplace();
    358             return *this;
    359         }
    360 
    361         if (this->isAllocated() && this->mCapacity < other.size()) {
    362             // Not enough dynamic memory, switch to in-place.
    363             this->dtor();
    364             init_inplace();
    365         } else {
    366             // This could potentially be improved by move-assigning
    367             // only needed items and destroying the rest, but
    368             // destroy-all+construct-all is just simpler. For PODs it actually
    369             // is even faster as it's always a single memcpy().
    370             this->destruct(this->begin(), this->end());
    371         }
    372 
    373         // Move the whole |other| into the pre-cleaned memory
    374         const auto newEnd = std::uninitialized_copy(
    375                 std::make_move_iterator(other.begin()),
    376                 std::make_move_iterator(other.end()), this->mBegin);
    377         this->mEnd = newEnd;
    378         // |other| is valid as-is.
    379         return *this;
    380     }
    381 
    382     // Make sure we don't end up trying to move from an interface - it's just
    383     // inefficient with the current code.
    384     SmallFixedVector(base&& other) = delete;
    385     SmallFixedVector& operator=(base&& other) = delete;
    386 
    387 private:
    388     // A shortcut for initialization for in-place storage.
    389     void init_inplace() { this->init(mData.array, mData.array, kSmallSize); }
    390 
    391     // A union with empty constructor and destructor makes sure that the array
    392     // elements are not default-constructed in ctor and not destructed in dtor:
    393     // the class needs to be able manage their lifetime more precisely.
    394     union Data {
    395         alignas(size_type) T array[kSmallSize];
    396 
    397         Data() {}
    398         ~Data() {}
    399     } mData;
    400 };
    401 
    402 }  // namespace base
    403 }  // namespace android
    404