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