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