tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SmallPointerArray.h (7828B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* A vector of pointers space-optimized for a small number of elements. */
      8 
      9 #ifndef mozilla_SmallPointerArray_h
     10 #define mozilla_SmallPointerArray_h
     11 
     12 #include "mozilla/Assertions.h"
     13 #include "mozilla/PodOperations.h"
     14 
     15 #include <algorithm>
     16 #include <cstddef>
     17 #include <new>
     18 #include <vector>
     19 
     20 namespace mozilla {
     21 
     22 // Array class for situations where a small number of NON-NULL elements (<= 2)
     23 // is expected, a large number of elements must be accommodated if necessary,
     24 // and the size of the class must be minimal. Typical vector implementations
     25 // will fulfill the first two requirements by simply adding inline storage
     26 // alongside the rest of their member variables. While this strategy works,
     27 // it brings unnecessary storage overhead for vectors with an expected small
     28 // number of elements. This class is intended to deal with that problem.
     29 //
     30 // This class is similar in performance to a vector class. Accessing its
     31 // elements when it has not grown over a size of 2 does not require an extra
     32 // level of indirection and will therefore be faster.
     33 //
     34 // The minimum (inline) size is 2 * sizeof(void*).
     35 //
     36 // Any modification of the array invalidates any outstanding iterators.
     37 template <typename T>
     38 class SmallPointerArray {
     39  // We cannot support SmallPointerArray<bool> because of iterator of
     40  // std::vector<bool> specialization that don't match iterator on pointer of
     41  // bool.
     42  static_assert(!std::is_same_v<T, bool>,
     43                "SmallPointerArray<bool> is not supported");
     44 
     45 public:
     46  SmallPointerArray() {
     47    // List-initialization would be nicer, but it only lets you initialize the
     48    // first union member.
     49    mArray[0].mValue = nullptr;
     50    mArray[1].mVector = nullptr;
     51  }
     52 
     53  ~SmallPointerArray() {
     54    if (!first()) {
     55      delete maybeVector();
     56    }
     57  }
     58 
     59  SmallPointerArray(SmallPointerArray&& aOther) {
     60    PodCopy(mArray, aOther.mArray, 2);
     61    aOther.mArray[0].mValue = nullptr;
     62    aOther.mArray[1].mVector = nullptr;
     63  }
     64 
     65  SmallPointerArray& operator=(SmallPointerArray&& aOther) {
     66    std::swap(mArray, aOther.mArray);
     67    return *this;
     68  }
     69 
     70  void Clear() {
     71    if (first()) {
     72      first() = nullptr;
     73      new (&mArray[1].mVector) std::vector<T*>*(nullptr);
     74      return;
     75    }
     76 
     77    delete maybeVector();
     78    mArray[1].mVector = nullptr;
     79  }
     80 
     81  void AppendElement(T* aElement) {
     82    // Storing nullptr as an element is not permitted, but we do check for it
     83    // to avoid corruption issues in non-debug builds.
     84 
     85    // In addition to this we assert in debug builds to point out mistakes to
     86    // users of the class.
     87    MOZ_ASSERT(aElement != nullptr);
     88    if (aElement == nullptr) {
     89      return;
     90    }
     91 
     92    if (!first()) {
     93      auto* vec = maybeVector();
     94      if (!vec) {
     95        first() = aElement;
     96        new (&mArray[1].mValue) T*(nullptr);
     97        return;
     98      }
     99 
    100      vec->push_back(aElement);
    101      return;
    102    }
    103 
    104    if (!second()) {
    105      second() = aElement;
    106      return;
    107    }
    108 
    109    auto* vec = new std::vector<T*>({first(), second(), aElement});
    110    first() = nullptr;
    111    new (&mArray[1].mVector) std::vector<T*>*(vec);
    112  }
    113 
    114  bool RemoveElement(T* aElement) {
    115    MOZ_ASSERT(aElement != nullptr);
    116    if (aElement == nullptr) {
    117      return false;
    118    }
    119 
    120    if (first() == aElement) {
    121      // Expected case.
    122      T* maybeSecond = second();
    123      first() = maybeSecond;
    124      if (maybeSecond) {
    125        second() = nullptr;
    126      } else {
    127        new (&mArray[1].mVector) std::vector<T*>*(nullptr);
    128      }
    129 
    130      return true;
    131    }
    132 
    133    if (first()) {
    134      if (second() == aElement) {
    135        second() = nullptr;
    136        return true;
    137      }
    138      return false;
    139    }
    140 
    141    if (auto* vec = maybeVector()) {
    142      for (auto iter = vec->begin(); iter != vec->end(); iter++) {
    143        if (*iter == aElement) {
    144          vec->erase(iter);
    145          return true;
    146        }
    147      }
    148    }
    149    return false;
    150  }
    151 
    152  bool Contains(T* aElement) const {
    153    MOZ_ASSERT(aElement != nullptr);
    154    if (aElement == nullptr) {
    155      return false;
    156    }
    157 
    158    if (T* v = first()) {
    159      return v == aElement || second() == aElement;
    160    }
    161 
    162    if (auto* vec = maybeVector()) {
    163      return std::find(vec->begin(), vec->end(), aElement) != vec->end();
    164    }
    165 
    166    return false;
    167  }
    168 
    169  size_t Length() const {
    170    if (first()) {
    171      return second() ? 2 : 1;
    172    }
    173 
    174    if (auto* vec = maybeVector()) {
    175      return vec->size();
    176    }
    177 
    178    return 0;
    179  }
    180 
    181  bool IsEmpty() const { return Length() == 0; }
    182 
    183  T* ElementAt(size_t aIndex) const {
    184    MOZ_ASSERT(aIndex < Length());
    185    if (first()) {
    186      return mArray[aIndex].mValue;
    187    }
    188 
    189    auto* vec = maybeVector();
    190    MOZ_ASSERT(vec, "must have backing vector if accessing an element");
    191    return (*vec)[aIndex];
    192  }
    193 
    194  T* operator[](size_t aIndex) const { return ElementAt(aIndex); }
    195 
    196  using iterator = T**;
    197  using const_iterator = const T**;
    198 
    199  // Methods for range-based for loops. Manipulation invalidates these.
    200  iterator begin() { return beginInternal(); }
    201  const_iterator begin() const { return beginInternal(); }
    202  const_iterator cbegin() const { return begin(); }
    203  iterator end() { return beginInternal() + Length(); }
    204  const_iterator end() const { return beginInternal() + Length(); }
    205  const_iterator cend() const { return end(); }
    206 
    207 private:
    208  T** beginInternal() const {
    209    if (first()) {
    210      static_assert(sizeof(T*) == sizeof(Element),
    211                    "pointer ops on &first() must produce adjacent "
    212                    "Element::mValue arms");
    213      return &first();
    214    }
    215 
    216    auto* vec = maybeVector();
    217    if (!vec) {
    218      return &first();
    219    }
    220 
    221    if (vec->empty()) {
    222      return nullptr;
    223    }
    224 
    225    return &(*vec)[0];
    226  }
    227 
    228  // Accessors for |mArray| element union arms.
    229 
    230  T*& first() const { return const_cast<T*&>(mArray[0].mValue); }
    231 
    232  T*& second() const {
    233    MOZ_ASSERT(first(), "first() must be non-null to have a T* second pointer");
    234    return const_cast<T*&>(mArray[1].mValue);
    235  }
    236 
    237  std::vector<T*>* maybeVector() const {
    238    MOZ_ASSERT(!first(),
    239               "function must only be called when this is either empty or has "
    240               "std::vector-backed elements");
    241    return mArray[1].mVector;
    242  }
    243 
    244  // In C++ active-union-arm terms:
    245  //
    246  //   - mArray[0].mValue is always active: a possibly null T*;
    247  //   - if mArray[0].mValue is null, mArray[1].mVector is active: a possibly
    248  //     null std::vector<T*>*; if mArray[0].mValue isn't null, mArray[1].mValue
    249  //     is active: a possibly null T*.
    250  //
    251  // SmallPointerArray begins empty, with mArray[1].mVector active and null.
    252  // Code that makes mArray[0].mValue non-null, i.e. assignments to first(),
    253  // must placement-new mArray[1].mValue with the proper value; code that goes
    254  // the opposite direction, making mArray[0].mValue null, must placement-new
    255  // mArray[1].mVector with the proper value.
    256  //
    257  // When !mArray[0].mValue && !mArray[1].mVector, the array is empty.
    258  //
    259  // When mArray[0].mValue && !mArray[1].mValue, the array has size 1 and
    260  // contains mArray[0].mValue.
    261  //
    262  // When mArray[0] && mArray[1], the array has size 2 and contains
    263  // mArray[0].mValue and mArray[1].mValue.
    264  //
    265  // When !mArray[0].mValue && mArray[1].mVector, mArray[1].mVector contains
    266  // the contents of an array of arbitrary size (even less than two if it ever
    267  // contained three elements and elements were removed).
    268  union Element {
    269    T* mValue;
    270    std::vector<T*>* mVector;
    271  } mArray[2];
    272 };
    273 
    274 }  // namespace mozilla
    275 
    276 #endif  // mozilla_SmallPointerArray_h