• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
~SmallVector()90     ~SmallVector() { dtor(); }
91 
92     // std::vector<> interface operations.
begin()93     iterator begin() { return mBegin; }
begin()94     const_iterator begin() const { return mBegin; }
cbegin()95     const_iterator cbegin() const { return mBegin; }
96 
end()97     iterator end() { return mEnd; }
end()98     const_iterator end() const { return mEnd; }
cend()99     const_iterator cend() const { return mEnd; }
100 
size()101     size_type size() const { return end() - begin(); }
capacity()102     size_type capacity() const { return mCapacity; }
empty()103     bool empty() const { return begin() == end(); }
104 
front()105     reference front() { return *begin(); }
front()106     const_reference front() const { return *cbegin(); }
back()107     reference back() { return *(end() - 1); }
back()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 
data()113     pointer data() { return mBegin; }
data()114     const_pointer data() const { return mBegin; }
cdata()115     const_pointer cdata() const { return mBegin; }
116 
117     template <class... Args>
emplace_back(Args &&...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 
push_back(const T & t)124     void push_back(const T& t) { emplace_back(t); }
push_back(T && t)125     void push_back(T&& t) { emplace_back(std::move(t)); }
126 
pop_back()127     void pop_back() {
128         destruct(mEnd - 1, mEnd);
129         --mEnd;
130     }
131 
clear()132     void clear() {
133         destruct(begin(), end());
134         mEnd = mBegin;
135     }
136 
reserve(size_type newCap)137     void reserve(size_type newCap) {
138         if (newCap <= this->capacity()) {
139             return;
140         }
141         set_capacity(newCap);
142     }
143 
resize(size_type newSize)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.
resize_noinit(size_type newSize)150     void resize_noinit(size_type newSize) { resize_impl<false>(newSize); }
151 
152     // Returns if the current vector's buffer is dynamically allocated.
isAllocated()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.
dtor()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.
init(iterator begin,iterator end,size_type capacity)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>
resize_impl(size_type newSize)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>
insert_back(Iter b,Iter e)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().
grow_for_size(size_type newSize)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().
set_capacity(size_type newCap)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.
destruct(T * b,T * e)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.
smallBufferStart()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.
SmallFixedVector()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>
SmallFixedVector(Iter b,Iter e)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>>
SmallFixedVector(const Range & r)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>>
SmallFixedVector(Range && r)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>>
SmallFixedVector(std::initializer_list<U> list)319     SmallFixedVector(std::initializer_list<U> list)
320         : SmallFixedVector(std::begin(list), std::end(list)) {}
321 
SmallFixedVector(const SmallFixedVector & other)322     SmallFixedVector(const SmallFixedVector& other)
323         : SmallFixedVector(other.begin(), other.end()) {}
324 
SmallFixedVector(SmallFixedVector && other)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.
init_inplace()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 
Data()397         Data() {}
~Data()398         ~Data() {}
399     } mData;
400 };
401 
402 }  // namespace base
403 }  // namespace android
404