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