1 // Copyright (c) 2018 Google LLC 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 #ifndef SOURCE_UTIL_SMALL_VECTOR_H_ 16 #define SOURCE_UTIL_SMALL_VECTOR_H_ 17 18 #include <cassert> 19 #include <iostream> 20 #include <memory> 21 #include <utility> 22 #include <vector> 23 24 #include "source/util/make_unique.h" 25 26 namespace spvtools { 27 namespace utils { 28 29 // The |SmallVector| class is intended to be a drop-in replacement for 30 // |std::vector|. The difference is in the implementation. A |SmallVector| is 31 // optimized for when the number of elements in the vector are small. Small is 32 // defined by the template parameter |small_size|. 33 // 34 // Note that |SmallVector| is not always faster than an |std::vector|, so you 35 // should experiment with different values for |small_size| and compare to 36 // using and |std::vector|. 37 // 38 // TODO: I have implemented the public member functions from |std::vector| that 39 // I needed. If others are needed they should be implemented. Do not implement 40 // public member functions that are not defined by std::vector. 41 template <class T, size_t small_size> 42 class SmallVector { 43 public: 44 using iterator = T*; 45 using const_iterator = const T*; 46 SmallVector()47 SmallVector() 48 : size_(0), 49 small_data_(reinterpret_cast<T*>(buffer)), 50 large_data_(nullptr) {} 51 SmallVector(const SmallVector & that)52 SmallVector(const SmallVector& that) : SmallVector() { *this = that; } 53 SmallVector(SmallVector && that)54 SmallVector(SmallVector&& that) : SmallVector() { *this = std::move(that); } 55 SmallVector(const std::vector<T> & vec)56 SmallVector(const std::vector<T>& vec) : SmallVector() { 57 if (vec.size() > small_size) { 58 large_data_ = MakeUnique<std::vector<T>>(vec); 59 } else { 60 size_ = vec.size(); 61 for (uint32_t i = 0; i < size_; i++) { 62 new (small_data_ + i) T(vec[i]); 63 } 64 } 65 } 66 SmallVector(std::vector<T> && vec)67 SmallVector(std::vector<T>&& vec) : SmallVector() { 68 if (vec.size() > small_size) { 69 large_data_ = MakeUnique<std::vector<T>>(std::move(vec)); 70 } else { 71 size_ = vec.size(); 72 for (uint32_t i = 0; i < size_; i++) { 73 new (small_data_ + i) T(std::move(vec[i])); 74 } 75 } 76 vec.clear(); 77 } 78 SmallVector(std::initializer_list<T> init_list)79 SmallVector(std::initializer_list<T> init_list) : SmallVector() { 80 if (init_list.size() < small_size) { 81 for (auto it = init_list.begin(); it != init_list.end(); ++it) { 82 new (small_data_ + (size_++)) T(std::move(*it)); 83 } 84 } else { 85 large_data_ = MakeUnique<std::vector<T>>(std::move(init_list)); 86 } 87 } 88 SmallVector(size_t s,const T & v)89 SmallVector(size_t s, const T& v) : SmallVector() { resize(s, v); } 90 ~SmallVector()91 virtual ~SmallVector() { 92 for (T* p = small_data_; p < small_data_ + size_; ++p) { 93 p->~T(); 94 } 95 } 96 97 SmallVector& operator=(const SmallVector& that) { 98 assert(small_data_); 99 if (that.large_data_) { 100 if (large_data_) { 101 *large_data_ = *that.large_data_; 102 } else { 103 large_data_ = MakeUnique<std::vector<T>>(*that.large_data_); 104 } 105 } else { 106 large_data_.reset(nullptr); 107 size_t i = 0; 108 // Do a copy for any element in |this| that is already constructed. 109 for (; i < size_ && i < that.size_; ++i) { 110 small_data_[i] = that.small_data_[i]; 111 } 112 113 if (i >= that.size_) { 114 // If the size of |this| becomes smaller after the assignment, then 115 // destroy any extra elements. 116 for (; i < size_; ++i) { 117 small_data_[i].~T(); 118 } 119 } else { 120 // If the size of |this| becomes larger after the assignement, copy 121 // construct the new elements that are needed. 122 for (; i < that.size_; ++i) { 123 new (small_data_ + i) T(that.small_data_[i]); 124 } 125 } 126 size_ = that.size_; 127 } 128 return *this; 129 } 130 131 SmallVector& operator=(SmallVector&& that) { 132 if (that.large_data_) { 133 large_data_.reset(that.large_data_.release()); 134 } else { 135 large_data_.reset(nullptr); 136 size_t i = 0; 137 // Do a move for any element in |this| that is already constructed. 138 for (; i < size_ && i < that.size_; ++i) { 139 small_data_[i] = std::move(that.small_data_[i]); 140 } 141 142 if (i >= that.size_) { 143 // If the size of |this| becomes smaller after the assignment, then 144 // destroy any extra elements. 145 for (; i < size_; ++i) { 146 small_data_[i].~T(); 147 } 148 } else { 149 // If the size of |this| becomes larger after the assignement, move 150 // construct the new elements that are needed. 151 for (; i < that.size_; ++i) { 152 new (small_data_ + i) T(std::move(that.small_data_[i])); 153 } 154 } 155 size_ = that.size_; 156 } 157 158 // Reset |that| because all of the data has been moved to |this|. 159 that.DestructSmallData(); 160 return *this; 161 } 162 163 template <class OtherVector> 164 friend bool operator==(const SmallVector& lhs, const OtherVector& rhs) { 165 if (lhs.size() != rhs.size()) { 166 return false; 167 } 168 169 auto rit = rhs.begin(); 170 for (auto lit = lhs.begin(); lit != lhs.end(); ++lit, ++rit) { 171 if (*lit != *rit) { 172 return false; 173 } 174 } 175 return true; 176 } 177 178 // Avoid infinite recursion from rewritten operators in C++20 179 #if __cplusplus <= 201703L 180 friend bool operator==(const std::vector<T>& lhs, const SmallVector& rhs) { 181 return rhs == lhs; 182 } 183 #endif 184 185 friend bool operator!=(const SmallVector& lhs, const std::vector<T>& rhs) { 186 return !(lhs == rhs); 187 } 188 189 friend bool operator!=(const std::vector<T>& lhs, const SmallVector& rhs) { 190 return rhs != lhs; 191 } 192 193 T& operator[](size_t i) { 194 if (!large_data_) { 195 return small_data_[i]; 196 } else { 197 return (*large_data_)[i]; 198 } 199 } 200 201 const T& operator[](size_t i) const { 202 if (!large_data_) { 203 return small_data_[i]; 204 } else { 205 return (*large_data_)[i]; 206 } 207 } 208 size()209 size_t size() const { 210 if (!large_data_) { 211 return size_; 212 } else { 213 return large_data_->size(); 214 } 215 } 216 begin()217 iterator begin() { 218 if (large_data_) { 219 return large_data_->data(); 220 } else { 221 return small_data_; 222 } 223 } 224 begin()225 const_iterator begin() const { 226 if (large_data_) { 227 return large_data_->data(); 228 } else { 229 return small_data_; 230 } 231 } 232 cbegin()233 const_iterator cbegin() const { return begin(); } 234 end()235 iterator end() { 236 if (large_data_) { 237 return large_data_->data() + large_data_->size(); 238 } else { 239 return small_data_ + size_; 240 } 241 } 242 end()243 const_iterator end() const { 244 if (large_data_) { 245 return large_data_->data() + large_data_->size(); 246 } else { 247 return small_data_ + size_; 248 } 249 } 250 cend()251 const_iterator cend() const { return end(); } 252 data()253 T* data() { return begin(); } 254 data()255 const T* data() const { return cbegin(); } 256 front()257 T& front() { return (*this)[0]; } 258 front()259 const T& front() const { return (*this)[0]; } 260 erase(const_iterator pos)261 iterator erase(const_iterator pos) { return erase(pos, pos + 1); } 262 erase(const_iterator first,const_iterator last)263 iterator erase(const_iterator first, const_iterator last) { 264 if (large_data_) { 265 size_t start_index = first - large_data_->data(); 266 size_t end_index = last - large_data_->data(); 267 auto r = large_data_->erase(large_data_->begin() + start_index, 268 large_data_->begin() + end_index); 269 return large_data_->data() + (r - large_data_->begin()); 270 } 271 272 // Since C++11, std::vector has |const_iterator| for the parameters, so I 273 // follow that. However, I need iterators to modify the current container, 274 // which is not const. This is why I cast away the const. 275 iterator f = const_cast<iterator>(first); 276 iterator l = const_cast<iterator>(last); 277 iterator e = end(); 278 279 size_t num_of_del_elements = last - first; 280 iterator ret = f; 281 if (first == last) { 282 return ret; 283 } 284 285 // Move |last| and any elements after it their earlier position. 286 while (l != e) { 287 *f = std::move(*l); 288 ++f; 289 ++l; 290 } 291 292 // Destroy the elements that were supposed to be deleted. 293 while (f != l) { 294 f->~T(); 295 ++f; 296 } 297 298 // Update the size. 299 size_ -= num_of_del_elements; 300 return ret; 301 } 302 push_back(const T & value)303 void push_back(const T& value) { 304 if (!large_data_ && size_ == small_size) { 305 MoveToLargeData(); 306 } 307 308 if (large_data_) { 309 large_data_->push_back(value); 310 return; 311 } 312 313 new (small_data_ + size_) T(value); 314 ++size_; 315 } 316 push_back(T && value)317 void push_back(T&& value) { 318 if (!large_data_ && size_ == small_size) { 319 MoveToLargeData(); 320 } 321 322 if (large_data_) { 323 large_data_->push_back(std::move(value)); 324 return; 325 } 326 327 new (small_data_ + size_) T(std::move(value)); 328 ++size_; 329 } 330 331 template <class InputIt> insert(iterator pos,InputIt first,InputIt last)332 iterator insert(iterator pos, InputIt first, InputIt last) { 333 size_t element_idx = (pos - begin()); 334 size_t num_of_new_elements = std::distance(first, last); 335 size_t new_size = size_ + num_of_new_elements; 336 if (!large_data_ && new_size > small_size) { 337 MoveToLargeData(); 338 } 339 340 if (large_data_) { 341 typename std::vector<T>::iterator new_pos = 342 large_data_->begin() + element_idx; 343 large_data_->insert(new_pos, first, last); 344 return begin() + element_idx; 345 } 346 347 // Move |pos| and all of the elements after it over |num_of_new_elements| 348 // places. We start at the end and work backwards, to make sure we do not 349 // overwrite data that we have not moved yet. 350 for (iterator i = begin() + new_size - 1, j = end() - 1; j >= pos; 351 --i, --j) { 352 if (i >= begin() + size_) { 353 new (i) T(std::move(*j)); 354 } else { 355 *i = std::move(*j); 356 } 357 } 358 359 // Copy the new elements into position. 360 iterator p = pos; 361 for (; first != last; ++p, ++first) { 362 if (p >= small_data_ + size_) { 363 new (p) T(*first); 364 } else { 365 *p = *first; 366 } 367 } 368 369 // Update the size. 370 size_ += num_of_new_elements; 371 return pos; 372 } 373 empty()374 bool empty() const { 375 if (large_data_) { 376 return large_data_->empty(); 377 } 378 return size_ == 0; 379 } 380 clear()381 void clear() { 382 if (large_data_) { 383 large_data_->clear(); 384 } else { 385 DestructSmallData(); 386 } 387 } 388 389 template <class... Args> emplace_back(Args &&...args)390 void emplace_back(Args&&... args) { 391 if (!large_data_ && size_ == small_size) { 392 MoveToLargeData(); 393 } 394 395 if (large_data_) { 396 large_data_->emplace_back(std::forward<Args>(args)...); 397 } else { 398 new (small_data_ + size_) T(std::forward<Args>(args)...); 399 ++size_; 400 } 401 } 402 resize(size_t new_size,const T & v)403 void resize(size_t new_size, const T& v) { 404 if (!large_data_ && new_size > small_size) { 405 MoveToLargeData(); 406 } 407 408 if (large_data_) { 409 large_data_->resize(new_size, v); 410 return; 411 } 412 413 // If |new_size| < |size_|, then destroy the extra elements. 414 for (size_t i = new_size; i < size_; ++i) { 415 small_data_[i].~T(); 416 } 417 418 // If |new_size| > |size_|, the copy construct the new elements. 419 for (size_t i = size_; i < new_size; ++i) { 420 new (small_data_ + i) T(v); 421 } 422 423 // Update the size. 424 size_ = new_size; 425 } 426 427 private: 428 // Moves all of the element from |small_data_| into a new std::vector that can 429 // be access through |large_data|. MoveToLargeData()430 void MoveToLargeData() { 431 assert(!large_data_); 432 large_data_ = MakeUnique<std::vector<T>>(); 433 for (size_t i = 0; i < size_; ++i) { 434 large_data_->emplace_back(std::move(small_data_[i])); 435 } 436 DestructSmallData(); 437 } 438 439 // Destroys all of the elements in |small_data_| that have been constructed. DestructSmallData()440 void DestructSmallData() { 441 for (size_t i = 0; i < size_; ++i) { 442 small_data_[i].~T(); 443 } 444 size_ = 0; 445 } 446 447 // The number of elements in |small_data_| that have been constructed. 448 size_t size_; 449 450 // The pointed used to access the array of elements when the number of 451 // elements is small. 452 T* small_data_; 453 454 // The actual data used to store the array elements. It must never be used 455 // directly, but must only be accessed through |small_data_|. 456 typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type 457 buffer[small_size]; 458 459 // A pointer to a vector that is used to store the elements of the vector when 460 // this size exceeds |small_size|. If |large_data_| is nullptr, then the data 461 // is stored in |small_data_|. Otherwise, the data is stored in 462 // |large_data_|. 463 std::unique_ptr<std::vector<T>> large_data_; 464 }; // namespace utils 465 466 } // namespace utils 467 } // namespace spvtools 468 469 #endif // SOURCE_UTIL_SMALL_VECTOR_H_ 470