1 // Copyright 2012 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_CONTAINERS_SMALL_MAP_H_ 6 #define BASE_CONTAINERS_SMALL_MAP_H_ 7 8 #include <stddef.h> 9 10 #include <limits> 11 #include <map> 12 #include <new> 13 #include <utility> 14 15 #include "base/check.h" 16 #include "base/check_op.h" 17 #include "base/memory/raw_ptr.h" 18 19 inline constexpr size_t kUsingFullMapSentinel = 20 std::numeric_limits<size_t>::max(); 21 22 namespace base { 23 24 // small_map is a container with a std::map-like interface. It starts out backed 25 // by an unsorted array but switches to some other container type if it grows 26 // beyond this fixed size. 27 // 28 // Please see //base/containers/README.md for an overview of which container 29 // to select. 30 // 31 // PROS 32 // 33 // - Good memory locality and low overhead for smaller maps. 34 // - Handles large maps without the degenerate performance of flat_map. 35 // 36 // CONS 37 // 38 // - Larger code size than the alternatives. 39 // 40 // IMPORTANT NOTES 41 // 42 // - Iterators are invalidated across mutations. 43 // 44 // DETAILS 45 // 46 // base::small_map will pick up the comparator from the underlying map type. In 47 // std::map only a "less" operator is defined, which requires us to do two 48 // comparisons per element when doing the brute-force search in the simple 49 // array. std::unordered_map has a key_equal function which will be used. 50 // 51 // We define default overrides for the common map types to avoid this 52 // double-compare, but you should be aware of this if you use your own operator< 53 // for your map and supply your own version of == to the small_map. You can use 54 // regular operator== by just doing: 55 // 56 // base::small_map<std::map<MyKey, MyValue>, 4, std::equal_to<KyKey>> 57 // 58 // 59 // USAGE 60 // ----- 61 // 62 // NormalMap: The map type to fall back to. This also defines the key and value 63 // types for the small_map. 64 // kArraySize: The size of the initial array of results. This will be allocated 65 // with the small_map object rather than separately on the heap. 66 // Once the map grows beyond this size, the map type will be used 67 // instead. 68 // EqualKey: A functor which tests two keys for equality. If the wrapped map 69 // type has a "key_equal" member (unordered_map does), then that will 70 // be used by default. If the wrapped map type has a strict weak 71 // ordering "key_compare" (std::map does), that will be used to 72 // implement equality by default. 73 // MapInit: A functor that takes a NormalMap* and uses it to initialize the map. 74 // This functor will be called at most once per small_map, when the map 75 // exceeds the threshold of kArraySize and we are about to copy values 76 // from the array to the map. The functor *must* initialize the 77 // NormalMap* argument with placement new, since after it runs we 78 // assume that the NormalMap has been initialized. 79 // 80 // Example: 81 // base::small_map<std::map<string, int>> days; 82 // days["sunday" ] = 0; 83 // days["monday" ] = 1; 84 // days["tuesday" ] = 2; 85 // days["wednesday"] = 3; 86 // days["thursday" ] = 4; 87 // days["friday" ] = 5; 88 // days["saturday" ] = 6; 89 90 namespace internal { 91 92 template <typename NormalMap> 93 class small_map_default_init { 94 public: operator()95 void operator()(NormalMap* map) const { new (map) NormalMap(); } 96 }; 97 98 // has_key_equal<M>::value is true iff there exists a type M::key_equal. This is 99 // used to dispatch to one of the select_equal_key<> metafunctions below. 100 template <typename M> 101 struct has_key_equal { 102 typedef char sml; // "small" is sometimes #defined so we use an abbreviation. 103 typedef struct { char dummy[2]; } big; 104 // Two functions, one accepts types that have a key_equal member, and one that 105 // accepts anything. They each return a value of a different size, so we can 106 // determine at compile-time which function would have been called. 107 template <typename U> static big test(typename U::key_equal*); 108 template <typename> static sml test(...); 109 // Determines if M::key_equal exists by looking at the size of the return 110 // type of the compiler-chosen test() function. 111 static const bool value = (sizeof(test<M>(0)) == sizeof(big)); 112 }; 113 template <typename M> const bool has_key_equal<M>::value; 114 115 // Base template used for map types that do NOT have an M::key_equal member, 116 // e.g., std::map<>. These maps have a strict weak ordering comparator rather 117 // than an equality functor, so equality will be implemented in terms of that 118 // comparator. 119 // 120 // There's a partial specialization of this template below for map types that do 121 // have an M::key_equal member. 122 template <typename M, bool has_key_equal_value> 123 struct select_equal_key { 124 struct equal_key { operatorselect_equal_key::equal_key125 bool operator()(const typename M::key_type& left, 126 const typename M::key_type& right) { 127 // Implements equality in terms of a strict weak ordering comparator. 128 typename M::key_compare comp; 129 return !comp(left, right) && !comp(right, left); 130 } 131 }; 132 }; 133 134 // Partial template specialization handles case where M::key_equal exists, e.g., 135 // unordered_map<>. 136 template <typename M> 137 struct select_equal_key<M, true> { 138 typedef typename M::key_equal equal_key; 139 }; 140 141 } // namespace internal 142 143 template <typename NormalMap, 144 size_t kArraySize = 4, 145 typename EqualKey = typename internal::select_equal_key< 146 NormalMap, 147 internal::has_key_equal<NormalMap>::value>::equal_key, 148 typename MapInit = internal::small_map_default_init<NormalMap>> 149 class small_map { 150 static_assert(kArraySize > 0, "Initial size must be greater than 0"); 151 static_assert(kArraySize != kUsingFullMapSentinel, 152 "Initial size out of range"); 153 154 public: 155 typedef typename NormalMap::key_type key_type; 156 typedef typename NormalMap::mapped_type data_type; 157 typedef typename NormalMap::mapped_type mapped_type; 158 typedef typename NormalMap::value_type value_type; 159 typedef EqualKey key_equal; 160 161 small_map() : size_(0), functor_(MapInit()) {} 162 163 explicit small_map(const MapInit& functor) : size_(0), functor_(functor) {} 164 165 // Allow copy-constructor and assignment, since STL allows them too. 166 small_map(const small_map& src) { 167 // size_ and functor_ are initted in InitFrom() 168 InitFrom(src); 169 } 170 171 void operator=(const small_map& src) { 172 if (&src == this) return; 173 174 // This is not optimal. If src and dest are both using the small array, we 175 // could skip the teardown and reconstruct. One problem to be resolved is 176 // that the value_type itself is pair<const K, V>, and const K is not 177 // assignable. 178 Destroy(); 179 InitFrom(src); 180 } 181 182 ~small_map() { Destroy(); } 183 184 class const_iterator; 185 186 class iterator { 187 public: 188 typedef typename NormalMap::iterator::iterator_category iterator_category; 189 typedef typename NormalMap::iterator::value_type value_type; 190 typedef typename NormalMap::iterator::difference_type difference_type; 191 typedef typename NormalMap::iterator::pointer pointer; 192 typedef typename NormalMap::iterator::reference reference; 193 194 inline iterator() : array_iter_(nullptr) {} 195 196 inline iterator& operator++() { 197 if (array_iter_ != nullptr) { 198 ++array_iter_; 199 } else { 200 ++map_iter_; 201 } 202 return *this; 203 } 204 205 inline iterator operator++(int /*unused*/) { 206 iterator result(*this); 207 ++(*this); 208 return result; 209 } 210 211 inline iterator& operator--() { 212 if (array_iter_ != nullptr) { 213 --array_iter_; 214 } else { 215 --map_iter_; 216 } 217 return *this; 218 } 219 220 inline iterator operator--(int /*unused*/) { 221 iterator result(*this); 222 --(*this); 223 return result; 224 } 225 226 inline value_type* operator->() const { 227 return array_iter_ ? array_iter_.get() : map_iter_.operator->(); 228 } 229 230 inline value_type& operator*() const { 231 return array_iter_ ? *array_iter_ : *map_iter_; 232 } 233 234 inline bool operator==(const iterator& other) const { 235 if (array_iter_ != nullptr) { 236 return array_iter_ == other.array_iter_; 237 } else { 238 return other.array_iter_ == nullptr && map_iter_ == other.map_iter_; 239 } 240 } 241 242 inline bool operator!=(const iterator& other) const { 243 return !(*this == other); 244 } 245 246 private: 247 friend class small_map; 248 friend class const_iterator; 249 inline explicit iterator(value_type* init) : array_iter_(init) {} 250 inline explicit iterator(const typename NormalMap::iterator& init) 251 : array_iter_(nullptr), map_iter_(init) {} 252 253 raw_ptr<value_type, AllowPtrArithmetic> array_iter_; 254 typename NormalMap::iterator map_iter_; 255 }; 256 257 class const_iterator { 258 public: 259 typedef typename NormalMap::const_iterator::iterator_category 260 iterator_category; 261 typedef typename NormalMap::const_iterator::value_type value_type; 262 typedef typename NormalMap::const_iterator::difference_type difference_type; 263 typedef typename NormalMap::const_iterator::pointer pointer; 264 typedef typename NormalMap::const_iterator::reference reference; 265 266 inline const_iterator() : array_iter_(nullptr) {} 267 268 // Non-explicit constructor lets us convert regular iterators to const 269 // iterators. 270 inline const_iterator(const iterator& other) 271 : array_iter_(other.array_iter_), map_iter_(other.map_iter_) {} 272 273 inline const_iterator& operator++() { 274 if (array_iter_ != nullptr) { 275 ++array_iter_; 276 } else { 277 ++map_iter_; 278 } 279 return *this; 280 } 281 282 inline const_iterator operator++(int /*unused*/) { 283 const_iterator result(*this); 284 ++(*this); 285 return result; 286 } 287 288 inline const_iterator& operator--() { 289 if (array_iter_ != nullptr) { 290 --array_iter_; 291 } else { 292 --map_iter_; 293 } 294 return *this; 295 } 296 297 inline const_iterator operator--(int /*unused*/) { 298 const_iterator result(*this); 299 --(*this); 300 return result; 301 } 302 303 inline const value_type* operator->() const { 304 return array_iter_ ? array_iter_.get() : map_iter_.operator->(); 305 } 306 307 inline const value_type& operator*() const { 308 return array_iter_ ? *array_iter_ : *map_iter_; 309 } 310 311 inline bool operator==(const const_iterator& other) const { 312 if (array_iter_ != nullptr) { 313 return array_iter_ == other.array_iter_; 314 } 315 return other.array_iter_ == nullptr && map_iter_ == other.map_iter_; 316 } 317 318 inline bool operator!=(const const_iterator& other) const { 319 return !(*this == other); 320 } 321 322 private: 323 friend class small_map; 324 inline explicit const_iterator(const value_type* init) 325 : array_iter_(init) {} 326 inline explicit const_iterator( 327 const typename NormalMap::const_iterator& init) 328 : array_iter_(nullptr), map_iter_(init) {} 329 330 raw_ptr<const value_type, AllowPtrArithmetic> array_iter_; 331 typename NormalMap::const_iterator map_iter_; 332 }; 333 334 iterator find(const key_type& key) { 335 key_equal compare; 336 337 if (UsingFullMap()) { 338 return iterator(map()->find(key)); 339 } 340 341 for (size_t i = 0; i < size_; ++i) { 342 if (compare(array_[i].first, key)) { 343 return iterator(array_ + i); 344 } 345 } 346 return iterator(array_ + size_); 347 } 348 349 const_iterator find(const key_type& key) const { 350 key_equal compare; 351 352 if (UsingFullMap()) { 353 return const_iterator(map()->find(key)); 354 } 355 356 for (size_t i = 0; i < size_; ++i) { 357 if (compare(array_[i].first, key)) { 358 return const_iterator(array_ + i); 359 } 360 } 361 return const_iterator(array_ + size_); 362 } 363 364 // Invalidates iterators. 365 data_type& operator[](const key_type& key) { 366 key_equal compare; 367 368 if (UsingFullMap()) { 369 return map_[key]; 370 } 371 372 // Search backwards to favor recently-added elements. 373 for (size_t i = size_; i > 0; --i) { 374 const size_t index = i - 1; 375 if (compare(array_[index].first, key)) { 376 return array_[index].second; 377 } 378 } 379 380 if (size_ == kArraySize) { 381 ConvertToRealMap(); 382 return map_[key]; 383 } 384 385 DCHECK(size_ < kArraySize); 386 new (&array_[size_]) value_type(key, data_type()); 387 return array_[size_++].second; 388 } 389 390 // Invalidates iterators. 391 std::pair<iterator, bool> insert(const value_type& x) { 392 key_equal compare; 393 394 if (UsingFullMap()) { 395 std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x); 396 return std::make_pair(iterator(ret.first), ret.second); 397 } 398 399 for (size_t i = 0; i < size_; ++i) { 400 if (compare(array_[i].first, x.first)) { 401 return std::make_pair(iterator(array_ + i), false); 402 } 403 } 404 405 if (size_ == kArraySize) { 406 ConvertToRealMap(); // Invalidates all iterators! 407 std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x); 408 return std::make_pair(iterator(ret.first), ret.second); 409 } 410 411 DCHECK(size_ < kArraySize); 412 new (&array_[size_]) value_type(x); 413 return std::make_pair(iterator(array_ + size_++), true); 414 } 415 416 // Invalidates iterators. 417 template <class InputIterator> 418 void insert(InputIterator f, InputIterator l) { 419 while (f != l) { 420 insert(*f); 421 ++f; 422 } 423 } 424 425 // Invalidates iterators. 426 template <typename... Args> 427 std::pair<iterator, bool> emplace(Args&&... args) { 428 key_equal compare; 429 430 if (UsingFullMap()) { 431 std::pair<typename NormalMap::iterator, bool> ret = 432 map_.emplace(std::forward<Args>(args)...); 433 return std::make_pair(iterator(ret.first), ret.second); 434 } 435 436 value_type x(std::forward<Args>(args)...); 437 for (size_t i = 0; i < size_; ++i) { 438 if (compare(array_[i].first, x.first)) { 439 return std::make_pair(iterator(array_ + i), false); 440 } 441 } 442 443 if (size_ == kArraySize) { 444 ConvertToRealMap(); // Invalidates all iterators! 445 std::pair<typename NormalMap::iterator, bool> ret = 446 map_.emplace(std::move(x)); 447 return std::make_pair(iterator(ret.first), ret.second); 448 } 449 450 DCHECK(size_ < kArraySize); 451 new (&array_[size_]) value_type(std::move(x)); 452 return std::make_pair(iterator(array_ + size_++), true); 453 } 454 455 iterator begin() { 456 return UsingFullMap() ? iterator(map_.begin()) : iterator(array_); 457 } 458 459 const_iterator begin() const { 460 return UsingFullMap() ? const_iterator(map_.begin()) 461 : const_iterator(array_); 462 } 463 464 iterator end() { 465 return UsingFullMap() ? iterator(map_.end()) : iterator(array_ + size_); 466 } 467 468 const_iterator end() const { 469 return UsingFullMap() ? const_iterator(map_.end()) 470 : const_iterator(array_ + size_); 471 } 472 473 void clear() { 474 if (UsingFullMap()) { 475 map_.~NormalMap(); 476 } else { 477 for (size_t i = 0; i < size_; ++i) { 478 array_[i].~value_type(); 479 } 480 } 481 size_ = 0; 482 } 483 484 // Invalidates iterators. Returns iterator following the last removed element. 485 iterator erase(const iterator& position) { 486 if (UsingFullMap()) { 487 return iterator(map_.erase(position.map_iter_)); 488 } 489 490 size_t i = static_cast<size_t>(position.array_iter_ - array_); 491 // TODO(crbug.com/817982): When we have a checked iterator, this CHECK might 492 // not be necessary. 493 CHECK_LE(i, size_); 494 array_[i].~value_type(); 495 --size_; 496 if (i != size_) { 497 new (&array_[i]) value_type(std::move(array_[size_])); 498 array_[size_].~value_type(); 499 return iterator(array_ + i); 500 } 501 return end(); 502 } 503 504 size_t erase(const key_type& key) { 505 iterator iter = find(key); 506 if (iter == end()) { 507 return 0; 508 } 509 erase(iter); 510 return 1; 511 } 512 513 size_t count(const key_type& key) const { 514 return (find(key) == end()) ? 0 : 1; 515 } 516 517 size_t size() const { return UsingFullMap() ? map_.size() : size_; } 518 519 bool empty() const { return UsingFullMap() ? map_.empty() : size_ == 0; } 520 521 // Returns true if we have fallen back to using the underlying map 522 // representation. 523 bool UsingFullMap() const { return size_ == kUsingFullMapSentinel; } 524 525 inline NormalMap* map() { 526 CHECK(UsingFullMap()); 527 return &map_; 528 } 529 530 inline const NormalMap* map() const { 531 CHECK(UsingFullMap()); 532 return &map_; 533 } 534 535 private: 536 // When `size_ == kUsingFullMapSentinel`, we have switched storage strategies 537 // from `array_[kArraySize] to `NormalMap map_`. See ConvertToRealMap and 538 // UsingFullMap. 539 size_t size_; 540 541 MapInit functor_; 542 543 // We want to call constructors and destructors manually, but we don't want 544 // to allocate and deallocate the memory used for them separately. Since 545 // array_ and map_ are mutually exclusive, we'll put them in a union. 546 union { 547 value_type array_[kArraySize]; 548 NormalMap map_; 549 }; 550 551 void ConvertToRealMap() { 552 // Storage for the elements in the temporary array. This is intentionally 553 // declared as a union to avoid having to default-construct |kArraySize| 554 // elements, only to move construct over them in the initial loop. 555 union Storage { 556 Storage() {} 557 ~Storage() {} 558 value_type array[kArraySize]; 559 } temp; 560 561 // Move the current elements into a temporary array. 562 for (size_t i = 0; i < kArraySize; ++i) { 563 new (&temp.array[i]) value_type(std::move(array_[i])); 564 array_[i].~value_type(); 565 } 566 567 // Initialize the map. 568 size_ = kUsingFullMapSentinel; 569 functor_(&map_); 570 571 // Insert elements into it. 572 for (size_t i = 0; i < kArraySize; ++i) { 573 map_.insert(std::move(temp.array[i])); 574 temp.array[i].~value_type(); 575 } 576 } 577 578 // Helpers for constructors and destructors. 579 void InitFrom(const small_map& src) { 580 functor_ = src.functor_; 581 size_ = src.size_; 582 if (src.UsingFullMap()) { 583 functor_(&map_); 584 map_ = src.map_; 585 } else { 586 for (size_t i = 0; i < size_; ++i) { 587 new (&array_[i]) value_type(src.array_[i]); 588 } 589 } 590 } 591 592 void Destroy() { 593 if (UsingFullMap()) { 594 map_.~NormalMap(); 595 } else { 596 for (size_t i = 0; i < size_; ++i) { 597 array_[i].~value_type(); 598 } 599 } 600 } 601 }; 602 603 } // namespace base 604 605 #endif // BASE_CONTAINERS_SMALL_MAP_H_ 606