1 /*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #ifndef SkTArray_DEFINED
9 #define SkTArray_DEFINED
10
11 #include "include/core/SkMath.h"
12 #include "include/core/SkTypes.h"
13 #include "include/private/SkMalloc.h"
14 #include "include/private/SkSafe32.h"
15 #include "include/private/SkTLogic.h"
16 #include "include/private/SkTemplates.h"
17 #include "include/private/SkTo.h"
18
19 #include <string.h>
20 #include <initializer_list>
21 #include <memory>
22 #include <new>
23 #include <utility>
24
25 /** SkTArray<T> implements a typical, mostly std::vector-like array.
26 Each T will be default-initialized on allocation, and ~T will be called on destruction.
27
28 MEM_MOVE controls the behavior when a T needs to be moved (e.g. when the array is resized)
29 - true: T will be bit-copied via memcpy.
30 - false: T will be moved via move-constructors.
31
32 Modern implementations of std::vector<T> will generally provide similar performance
33 characteristics when used with appropriate care. Consider using std::vector<T> in new code.
34 */
35 template <typename T, bool MEM_MOVE = false> class SkTArray {
36 private:
37 enum ReallocType { kExactFit, kGrowing, kShrinking };
38
39 public:
40 using value_type = T;
41
42 /**
43 * Creates an empty array with no initial storage
44 */
SkTArray()45 SkTArray() { this->init(0); }
46
47 /**
48 * Creates an empty array that will preallocate space for reserveCount
49 * elements.
50 */
SkTArray(int reserveCount)51 explicit SkTArray(int reserveCount) : SkTArray() { this->reserve_back(reserveCount); }
52
53 /**
54 * Copies one array to another. The new array will be heap allocated.
55 */
SkTArray(const SkTArray & that)56 SkTArray(const SkTArray& that)
57 : SkTArray(that.fItemArray, that.fCount) {}
58
SkTArray(SkTArray && that)59 SkTArray(SkTArray&& that) {
60 if (that.fOwnMemory) {
61 fItemArray = that.fItemArray;
62 fCount = that.fCount;
63 fAllocCount = that.fAllocCount;
64 fOwnMemory = true;
65 fReserved = that.fReserved;
66
67 that.fItemArray = nullptr;
68 that.fCount = 0;
69 that.fAllocCount = 0;
70 that.fOwnMemory = true;
71 that.fReserved = false;
72 } else {
73 this->init(that.fCount);
74 that.move(fItemArray);
75 that.fCount = 0;
76 }
77 }
78
79 /**
80 * Creates a SkTArray by copying contents of a standard C array. The new
81 * array will be heap allocated. Be careful not to use this constructor
82 * when you really want the (void*, int) version.
83 */
SkTArray(const T * array,int count)84 SkTArray(const T* array, int count) {
85 this->init(count);
86 this->copy(array);
87 }
88 /**
89 * Creates a SkTArray by copying contents of an initializer list.
90 */
SkTArray(std::initializer_list<T> data)91 SkTArray(std::initializer_list<T> data)
92 : SkTArray(data.begin(), data.size()) {}
93
94 SkTArray& operator=(const SkTArray& that) {
95 if (this == &that) {
96 return *this;
97 }
98 for (int i = 0; i < this->count(); ++i) {
99 fItemArray[i].~T();
100 }
101 fCount = 0;
102 this->checkRealloc(that.count(), kExactFit);
103 fCount = that.fCount;
104 this->copy(that.fItemArray);
105 return *this;
106 }
107 SkTArray& operator=(SkTArray&& that) {
108 if (this == &that) {
109 return *this;
110 }
111 for (int i = 0; i < this->count(); ++i) {
112 fItemArray[i].~T();
113 }
114 fCount = 0;
115 this->checkRealloc(that.count(), kExactFit);
116 fCount = that.fCount;
117 that.move(fItemArray);
118 that.fCount = 0;
119 return *this;
120 }
121
~SkTArray()122 ~SkTArray() {
123 for (int i = 0; i < this->count(); ++i) {
124 fItemArray[i].~T();
125 }
126 if (fOwnMemory) {
127 sk_free(fItemArray);
128 }
129 }
130
131 /**
132 * Resets to count() == 0 and resets any reserve count.
133 */
reset()134 void reset() {
135 this->pop_back_n(fCount);
136 fReserved = false;
137 }
138
139 /**
140 * Resets to count() = n newly constructed T objects and resets any reserve count.
141 */
reset(int n)142 void reset(int n) {
143 SkASSERT(n >= 0);
144 for (int i = 0; i < this->count(); ++i) {
145 fItemArray[i].~T();
146 }
147 // Set fCount to 0 before calling checkRealloc so that no elements are moved.
148 fCount = 0;
149 this->checkRealloc(n, kExactFit);
150 fCount = n;
151 for (int i = 0; i < this->count(); ++i) {
152 new (fItemArray + i) T;
153 }
154 fReserved = false;
155 }
156
157 /**
158 * Resets to a copy of a C array and resets any reserve count.
159 */
reset(const T * array,int count)160 void reset(const T* array, int count) {
161 for (int i = 0; i < this->count(); ++i) {
162 fItemArray[i].~T();
163 }
164 fCount = 0;
165 this->checkRealloc(count, kExactFit);
166 fCount = count;
167 this->copy(array);
168 fReserved = false;
169 }
170
171 /**
172 * Ensures there is enough reserved space for n additional elements. The is guaranteed at least
173 * until the array size grows above n and subsequently shrinks below n, any version of reset()
174 * is called, or reserve_back() is called again.
175 */
reserve_back(int n)176 void reserve_back(int n) {
177 SkASSERT(n >= 0);
178 if (n > 0) {
179 this->checkRealloc(n, kExactFit);
180 fReserved = fOwnMemory;
181 } else {
182 fReserved = false;
183 }
184 }
185
removeShuffle(int n)186 void removeShuffle(int n) {
187 SkASSERT(n < this->count());
188 int newCount = fCount - 1;
189 fCount = newCount;
190 fItemArray[n].~T();
191 if (n != newCount) {
192 this->move(n, newCount);
193 }
194 }
195
196 /**
197 * Number of elements in the array.
198 */
count()199 int count() const { return fCount; }
200
201 /**
202 * Is the array empty.
203 */
empty()204 bool empty() const { return !fCount; }
205
206 /**
207 * Adds 1 new default-initialized T value and returns it by reference. Note
208 * the reference only remains valid until the next call that adds or removes
209 * elements.
210 */
push_back()211 T& push_back() {
212 void* newT = this->push_back_raw(1);
213 return *new (newT) T;
214 }
215
216 /**
217 * Version of above that uses a copy constructor to initialize the new item
218 */
push_back(const T & t)219 T& push_back(const T& t) {
220 void* newT = this->push_back_raw(1);
221 return *new (newT) T(t);
222 }
223
224 /**
225 * Version of above that uses a move constructor to initialize the new item
226 */
push_back(T && t)227 T& push_back(T&& t) {
228 void* newT = this->push_back_raw(1);
229 return *new (newT) T(std::move(t));
230 }
231
232 /**
233 * Construct a new T at the back of this array.
234 */
emplace_back(Args &&...args)235 template<class... Args> T& emplace_back(Args&&... args) {
236 void* newT = this->push_back_raw(1);
237 return *new (newT) T(std::forward<Args>(args)...);
238 }
239
240 /**
241 * Allocates n more default-initialized T values, and returns the address of
242 * the start of that new range. Note: this address is only valid until the
243 * next API call made on the array that might add or remove elements.
244 */
push_back_n(int n)245 T* push_back_n(int n) {
246 SkASSERT(n >= 0);
247 void* newTs = this->push_back_raw(n);
248 for (int i = 0; i < n; ++i) {
249 new (static_cast<char*>(newTs) + i * sizeof(T)) T;
250 }
251 return static_cast<T*>(newTs);
252 }
253
254 /**
255 * Version of above that uses a copy constructor to initialize all n items
256 * to the same T.
257 */
push_back_n(int n,const T & t)258 T* push_back_n(int n, const T& t) {
259 SkASSERT(n >= 0);
260 void* newTs = this->push_back_raw(n);
261 for (int i = 0; i < n; ++i) {
262 new (static_cast<char*>(newTs) + i * sizeof(T)) T(t);
263 }
264 return static_cast<T*>(newTs);
265 }
266
267 /**
268 * Version of above that uses a copy constructor to initialize the n items
269 * to separate T values.
270 */
push_back_n(int n,const T t[])271 T* push_back_n(int n, const T t[]) {
272 SkASSERT(n >= 0);
273 this->checkRealloc(n, kGrowing);
274 for (int i = 0; i < n; ++i) {
275 new (fItemArray + fCount + i) T(t[i]);
276 }
277 fCount += n;
278 return fItemArray + fCount - n;
279 }
280
281 /**
282 * Version of above that uses the move constructor to set n items.
283 */
move_back_n(int n,T * t)284 T* move_back_n(int n, T* t) {
285 SkASSERT(n >= 0);
286 this->checkRealloc(n, kGrowing);
287 for (int i = 0; i < n; ++i) {
288 new (fItemArray + fCount + i) T(std::move(t[i]));
289 }
290 fCount += n;
291 return fItemArray + fCount - n;
292 }
293
294 /**
295 * Removes the last element. Not safe to call when count() == 0.
296 */
pop_back()297 void pop_back() {
298 SkASSERT(fCount > 0);
299 --fCount;
300 fItemArray[fCount].~T();
301 this->checkRealloc(0, kShrinking);
302 }
303
304 /**
305 * Removes the last n elements. Not safe to call when count() < n.
306 */
pop_back_n(int n)307 void pop_back_n(int n) {
308 SkASSERT(n >= 0);
309 SkASSERT(this->count() >= n);
310 fCount -= n;
311 for (int i = 0; i < n; ++i) {
312 fItemArray[fCount + i].~T();
313 }
314 this->checkRealloc(0, kShrinking);
315 }
316
317 /**
318 * Pushes or pops from the back to resize. Pushes will be default
319 * initialized.
320 */
resize_back(int newCount)321 void resize_back(int newCount) {
322 SkASSERT(newCount >= 0);
323
324 if (newCount > this->count()) {
325 this->push_back_n(newCount - fCount);
326 } else if (newCount < this->count()) {
327 this->pop_back_n(fCount - newCount);
328 }
329 }
330
331 /** Swaps the contents of this array with that array. Does a pointer swap if possible,
332 otherwise copies the T values. */
swap(SkTArray & that)333 void swap(SkTArray& that) {
334 using std::swap;
335 if (this == &that) {
336 return;
337 }
338 if (fOwnMemory && that.fOwnMemory) {
339 swap(fItemArray, that.fItemArray);
340
341 auto count = fCount;
342 fCount = that.fCount;
343 that.fCount = count;
344
345 auto allocCount = fAllocCount;
346 fAllocCount = that.fAllocCount;
347 that.fAllocCount = allocCount;
348 } else {
349 // This could be more optimal...
350 SkTArray copy(std::move(that));
351 that = std::move(*this);
352 *this = std::move(copy);
353 }
354 }
355
begin()356 T* begin() {
357 return fItemArray;
358 }
begin()359 const T* begin() const {
360 return fItemArray;
361 }
end()362 T* end() {
363 return fItemArray ? fItemArray + fCount : nullptr;
364 }
end()365 const T* end() const {
366 return fItemArray ? fItemArray + fCount : nullptr;
367 }
data()368 T* data() { return fItemArray; }
data()369 const T* data() const { return fItemArray; }
size()370 size_t size() const { return (size_t)fCount; }
resize(size_t count)371 void resize(size_t count) { this->resize_back((int)count); }
372
373 /**
374 * Get the i^th element.
375 */
376 T& operator[] (int i) {
377 SkASSERT(i < this->count());
378 SkASSERT(i >= 0);
379 return fItemArray[i];
380 }
381
382 const T& operator[] (int i) const {
383 SkASSERT(i < this->count());
384 SkASSERT(i >= 0);
385 return fItemArray[i];
386 }
387
at(int i)388 T& at(int i) { return (*this)[i]; }
at(int i)389 const T& at(int i) const { return (*this)[i]; }
390
391 /**
392 * equivalent to operator[](0)
393 */
front()394 T& front() { SkASSERT(fCount > 0); return fItemArray[0];}
395
front()396 const T& front() const { SkASSERT(fCount > 0); return fItemArray[0];}
397
398 /**
399 * equivalent to operator[](count() - 1)
400 */
back()401 T& back() { SkASSERT(fCount); return fItemArray[fCount - 1];}
402
back()403 const T& back() const { SkASSERT(fCount > 0); return fItemArray[fCount - 1];}
404
405 /**
406 * equivalent to operator[](count()-1-i)
407 */
fromBack(int i)408 T& fromBack(int i) {
409 SkASSERT(i >= 0);
410 SkASSERT(i < this->count());
411 return fItemArray[fCount - i - 1];
412 }
413
fromBack(int i)414 const T& fromBack(int i) const {
415 SkASSERT(i >= 0);
416 SkASSERT(i < this->count());
417 return fItemArray[fCount - i - 1];
418 }
419
420 bool operator==(const SkTArray<T, MEM_MOVE>& right) const {
421 int leftCount = this->count();
422 if (leftCount != right.count()) {
423 return false;
424 }
425 for (int index = 0; index < leftCount; ++index) {
426 if (fItemArray[index] != right.fItemArray[index]) {
427 return false;
428 }
429 }
430 return true;
431 }
432
433 bool operator!=(const SkTArray<T, MEM_MOVE>& right) const {
434 return !(*this == right);
435 }
436
capacity()437 int capacity() const {
438 return fAllocCount;
439 }
440
441 protected:
442 /**
443 * Creates an empty array that will use the passed storage block until it
444 * is insufficiently large to hold the entire array.
445 */
446 template <int N>
SkTArray(SkAlignedSTStorage<N,T> * storage)447 SkTArray(SkAlignedSTStorage<N,T>* storage) {
448 this->initWithPreallocatedStorage(0, storage->get(), N);
449 }
450
451 /**
452 * Copy a C array, using preallocated storage if preAllocCount >=
453 * count. Otherwise storage will only be used when array shrinks
454 * to fit.
455 */
456 template <int N>
SkTArray(const T * array,int count,SkAlignedSTStorage<N,T> * storage)457 SkTArray(const T* array, int count, SkAlignedSTStorage<N,T>* storage) {
458 this->initWithPreallocatedStorage(count, storage->get(), N);
459 this->copy(array);
460 }
461
462 private:
init(int count)463 void init(int count) {
464 fCount = SkToU32(count);
465 if (!count) {
466 fAllocCount = 0;
467 fItemArray = nullptr;
468 } else {
469 fAllocCount = SkToU32(std::max(count, kMinHeapAllocCount));
470 fItemArray = (T*)sk_malloc_throw((size_t)fAllocCount, sizeof(T));
471 }
472 fOwnMemory = true;
473 fReserved = false;
474 }
475
initWithPreallocatedStorage(int count,void * preallocStorage,int preallocCount)476 void initWithPreallocatedStorage(int count, void* preallocStorage, int preallocCount) {
477 SkASSERT(count >= 0);
478 SkASSERT(preallocCount > 0);
479 SkASSERT(preallocStorage);
480 fCount = count;
481 fItemArray = nullptr;
482 fReserved = false;
483 if (count > preallocCount) {
484 fAllocCount = std::max(count, kMinHeapAllocCount);
485 fItemArray = (T*)sk_malloc_throw(fAllocCount, sizeof(T));
486 fOwnMemory = true;
487 } else {
488 fAllocCount = preallocCount;
489 fItemArray = (T*)preallocStorage;
490 fOwnMemory = false;
491 }
492 }
493
494 /** In the following move and copy methods, 'dst' is assumed to be uninitialized raw storage.
495 * In the following move methods, 'src' is destroyed leaving behind uninitialized raw storage.
496 */
copy(const T * src)497 void copy(const T* src) {
498 // Some types may be trivially copyable, in which case we *could* use memcopy; but
499 // MEM_MOVE == true implies that the type is trivially movable, and not necessarily
500 // trivially copyable (think sk_sp<>). So short of adding another template arg, we
501 // must be conservative and use copy construction.
502 for (int i = 0; i < this->count(); ++i) {
503 new (fItemArray + i) T(src[i]);
504 }
505 }
506
move(int dst,int src)507 template <bool E = MEM_MOVE> std::enable_if_t<E, void> move(int dst, int src) {
508 memcpy(&fItemArray[dst], &fItemArray[src], sizeof(T));
509 }
move(void * dst)510 template <bool E = MEM_MOVE> std::enable_if_t<E, void> move(void* dst) {
511 sk_careful_memcpy(dst, fItemArray, fCount * sizeof(T));
512 }
513
move(int dst,int src)514 template <bool E = MEM_MOVE> std::enable_if_t<!E, void> move(int dst, int src) {
515 new (&fItemArray[dst]) T(std::move(fItemArray[src]));
516 fItemArray[src].~T();
517 }
move(void * dst)518 template <bool E = MEM_MOVE> std::enable_if_t<!E, void> move(void* dst) {
519 for (int i = 0; i < this->count(); ++i) {
520 new (static_cast<char*>(dst) + sizeof(T) * (size_t)i) T(std::move(fItemArray[i]));
521 fItemArray[i].~T();
522 }
523 }
524
525 static constexpr int kMinHeapAllocCount = 8;
526
527 // Helper function that makes space for n objects, adjusts the count, but does not initialize
528 // the new objects.
push_back_raw(int n)529 void* push_back_raw(int n) {
530 this->checkRealloc(n, kGrowing);
531 void* ptr = fItemArray + fCount;
532 fCount += n;
533 return ptr;
534 }
535
checkRealloc(int delta,ReallocType reallocType)536 void checkRealloc(int delta, ReallocType reallocType) {
537 SkASSERT(fCount >= 0);
538 SkASSERT(fAllocCount >= 0);
539 SkASSERT(-delta <= this->count());
540
541 // Move into 64bit math temporarily, to avoid local overflows
542 int64_t newCount = fCount + delta;
543
544 // We allow fAllocCount to be in the range [newCount, 3*newCount]. We also never shrink
545 // when we're currently using preallocated memory, would allocate less than
546 // kMinHeapAllocCount, or a reserve count was specified that has yet to be exceeded.
547 bool mustGrow = newCount > fAllocCount;
548 bool shouldShrink = fAllocCount > 3 * newCount && fOwnMemory && !fReserved;
549 if (!mustGrow && !shouldShrink) {
550 return;
551 }
552
553 int64_t newAllocCount = newCount;
554 if (reallocType != kExactFit) {
555 // Whether we're growing or shrinking, leave at least 50% extra space for future growth.
556 newAllocCount += ((newCount + 1) >> 1);
557 // Align the new allocation count to kMinHeapAllocCount.
558 static_assert(SkIsPow2(kMinHeapAllocCount), "min alloc count not power of two.");
559 newAllocCount = (newAllocCount + (kMinHeapAllocCount - 1)) & ~(kMinHeapAllocCount - 1);
560 }
561
562 // At small sizes the old and new alloc count can both be kMinHeapAllocCount.
563 if (newAllocCount == fAllocCount) {
564 return;
565 }
566
567 fAllocCount = SkToU32(Sk64_pin_to_s32(newAllocCount));
568 SkASSERT(fAllocCount >= newCount);
569 T* newItemArray = (T*)sk_malloc_throw((size_t)fAllocCount, sizeof(T));
570 this->move(newItemArray);
571 if (fOwnMemory) {
572 sk_free(fItemArray);
573 }
574 fItemArray = newItemArray;
575 fOwnMemory = true;
576 fReserved = false;
577 }
578
579 T* fItemArray;
580 uint32_t fOwnMemory : 1;
581 uint32_t fCount : 31;
582 uint32_t fReserved : 1;
583 uint32_t fAllocCount : 31;
584 };
585
swap(SkTArray<T,M> & a,SkTArray<T,M> & b)586 template <typename T, bool M> static inline void swap(SkTArray<T, M>& a, SkTArray<T, M>& b) {
587 a.swap(b);
588 }
589
590 template<typename T, bool MEM_MOVE> constexpr int SkTArray<T, MEM_MOVE>::kMinHeapAllocCount;
591
592 /**
593 * Subclass of SkTArray that contains a preallocated memory block for the array.
594 */
595 template <int N, typename T, bool MEM_MOVE = false>
596 class SkSTArray : private SkAlignedSTStorage<N,T>, public SkTArray<T, MEM_MOVE> {
597 private:
598 using STORAGE = SkAlignedSTStorage<N,T>;
599 using INHERITED = SkTArray<T, MEM_MOVE>;
600
601 public:
SkSTArray()602 SkSTArray()
603 : STORAGE{}, INHERITED(static_cast<STORAGE*>(this)) {}
604
SkSTArray(const T * array,int count)605 SkSTArray(const T* array, int count)
606 : STORAGE{}, INHERITED(array, count, static_cast<STORAGE*>(this)) {}
607
SkSTArray(std::initializer_list<T> data)608 SkSTArray(std::initializer_list<T> data)
609 : SkSTArray(data.begin(), data.size()) {}
610
SkSTArray(int reserveCount)611 explicit SkSTArray(int reserveCount)
612 : SkSTArray() {
613 this->reserve_back(reserveCount);
614 }
615
SkSTArray(const SkSTArray & that)616 SkSTArray (const SkSTArray& that) : SkSTArray() { *this = that; }
SkSTArray(const INHERITED & that)617 explicit SkSTArray(const INHERITED& that) : SkSTArray() { *this = that; }
SkSTArray(SkSTArray && that)618 SkSTArray ( SkSTArray&& that) : SkSTArray() { *this = std::move(that); }
SkSTArray(INHERITED && that)619 explicit SkSTArray( INHERITED&& that) : SkSTArray() { *this = std::move(that); }
620
621 SkSTArray& operator=(const SkSTArray& that) {
622 INHERITED::operator=(that);
623 return *this;
624 }
625 SkSTArray& operator=(const INHERITED& that) {
626 INHERITED::operator=(that);
627 return *this;
628 }
629
630 SkSTArray& operator=(SkSTArray&& that) {
631 INHERITED::operator=(std::move(that));
632 return *this;
633 }
634 SkSTArray& operator=(INHERITED&& that) {
635 INHERITED::operator=(std::move(that));
636 return *this;
637 }
638 };
639
640 #endif
641