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