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