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