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