Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qarraydatapointer.h
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#ifndef QARRAYDATAPOINTER_H
6#define QARRAYDATAPOINTER_H
7
8#include <QtCore/qarraydataops.h>
9#include <QtCore/qcontainertools_impl.h>
10
11#include <QtCore/q20utility.h>
12
13QT_BEGIN_NAMESPACE
14
15template <class T>
16struct QArrayDataPointer
17{
18private:
19 typedef QTypedArrayData<T> Data;
20 typedef QArrayDataOps<T> DataOps;
21
22public:
23 enum {
24 pass_parameter_by_value = std::disjunction_v<
25 std::is_arithmetic<T>,
26 std::is_pointer<T>,
27 std::is_enum<T>
28 >,
29 };
30
31 typedef typename std::conditional<pass_parameter_by_value, T, const T &>::type parameter_type;
32
33 Q_NODISCARD_CTOR
34 constexpr QArrayDataPointer() = default;
35
36 Q_NODISCARD_CTOR
37 Q_DECL_CONSTEXPR_DTOR
38 QArrayDataPointer(const QArrayDataPointer &other) noexcept
39 : QArrayDataPointer(other.d, other.ptr, other.size)
40 {
41 ref();
42 }
43
44 Q_NODISCARD_CTOR
45 constexpr QArrayDataPointer(Data *header, T *adata, qsizetype n = 0) noexcept
46#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED)
47 : ptr(adata), size(n), d(header)
48#else
49 : d(header), ptr(adata), size(n)
50#endif
51 {
52 }
53
54 Q_NODISCARD_CTOR
55 explicit QArrayDataPointer(QTypedArrayAllocationResult<T> adata, qsizetype n = 0) noexcept
56 : QArrayDataPointer(adata.header, adata.ptr, n)
57 {
58 }
59
60 Q_NODISCARD_CTOR explicit
61 QArrayDataPointer(qsizetype alloc, qsizetype n = 0,
62 QArrayData::AllocationOption option = QArrayData::KeepSize)
63 : QArrayDataPointer(Data::allocate(alloc, option), n)
64 {
65 }
66
67 Q_NODISCARD_CTOR
68 static QArrayDataPointer fromRawData(const T *rawData, qsizetype length) noexcept
69 {
70 Q_ASSERT(rawData || !length);
71 return { nullptr, const_cast<T *>(rawData), length };
72 }
73
74 Q_DECL_CONSTEXPR_DTOR
75 QArrayDataPointer &operator=(const QArrayDataPointer &other) noexcept
76 {
77 QArrayDataPointer tmp(other);
78 this->swap(tmp);
79 return *this;
80 }
81
82 Q_NODISCARD_CTOR
83 constexpr
84 QArrayDataPointer(QArrayDataPointer &&other) noexcept
85 : QArrayDataPointer(q20::exchange(other.d, nullptr),
86 q20::exchange(other.ptr, nullptr),
87 q20::exchange(other.size, 0))
88 {
89 }
90
91 Q_DECL_CONSTEXPR_DTOR
92 QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QArrayDataPointer)
93
94 DataOps operator*() noexcept
95 {
96 return DataOps(*this);
97 }
98
99 DataOps operator->() noexcept
100 {
101 return DataOps(*this);
102 }
103
104 Q_DECL_CONSTEXPR_DTOR
105 ~QArrayDataPointer()
106 {
107 if (!deref())
108 destroyAndDeallocate();
109 }
110
111 // Avoid instantiating QArrayDataOps<T> inside the constexpr destructor.
112 void destroyAndDeallocate()
113 {
114 (*this)->destroyAll();
115 Data::deallocate(d);
116 }
117
118 constexpr bool isNull() const noexcept
119 {
120 return !ptr;
121 }
122
123 T *data() noexcept { T *p = ptr; if (size) Q_PRESUME(p); return p; }
124 constexpr const T *data() const noexcept { T *p = ptr; if (size) Q_PRESUME(p); return p; }
125
126 T *begin() noexcept { return data(); }
127 T *end() noexcept { return data() + size; }
128 constexpr
129 const T *begin() const noexcept { return data(); }
130 constexpr
131 const T *end() const noexcept { return data() + size; }
132 constexpr
133 const T *constBegin() const noexcept { return data(); }
134 constexpr
135 const T *constEnd() const noexcept { return data() + size; }
136
137 constexpr void swap(QArrayDataPointer &other) noexcept
138 {
139 qt_ptr_swap(d, other.d);
140 qt_ptr_swap(ptr, other.ptr);
141 std::swap(size, other.size);
142 }
143
144 void clear() noexcept(std::is_nothrow_destructible<T>::value)
145 {
146 QArrayDataPointer tmp;
147 swap(tmp);
148 }
149
150 void detach(QArrayDataPointer *old = nullptr)
151 {
152 if (needsDetach())
153 reallocateAndGrow(QArrayData::GrowsAtEnd, 0, old);
154 }
155
156 /*! \internal
157
158 Reinterprets the data of this QArrayDataPointer to type X. It's the
159 caller's responsibility to ensure that the data contents are valid and
160 properly aligned, particularly if T and X are not trivial types (i.e,
161 don't do that). The current size is kept and the allocated capacity is
162 updated to account for the difference in the element type's size.
163
164 This is used in QString::fromLatin1 to perform in-place conversion of
165 QString to QByteArray.
166 */
167 template <typename X> QArrayDataPointer<X> reinterpreted() &&
168 {
169 if (sizeof(T) != sizeof(X)) {
170 Q_ASSERT(!d->isShared());
171 d->alloc = d->alloc * sizeof(T) / sizeof(X);
172 }
173 auto od = reinterpret_cast<QTypedArrayData<X> *>(std::exchange(d, nullptr));
174 auto optr = reinterpret_cast<X *>(std::exchange(ptr, nullptr));
175 return { od, optr, std::exchange(size, 0) };
176 }
177
178 /*! \internal
179
180 Detaches this (optionally) and grows to accommodate the free space for
181 \a n elements at the required side. The side is determined from \a pos.
182
183 \a data pointer can be provided when the caller knows that \a data
184 points into range [this->begin(), this->end()). In case it is, *data
185 would be updated so that it continues to point to the element it was
186 pointing to before the data move. if \a data does not point into range,
187 one can/should pass \c nullptr.
188
189 Similarly to \a data, \a old, pointer to a default-constructed QADP, can
190 be provided when the caller expects to e.g. copy the data from this to
191 itself:
192 \code
193 QList<T> list(5);
194 qsizetype pos = getArbitraryPos();
195 list.insert(pos, list.begin(), list.end());
196 \endcode
197
198 The default rule would be: \a data and \a old must either both be valid
199 pointers, or both equal to \c nullptr.
200 */
201 void detachAndGrow(QArrayData::GrowthPosition where, qsizetype n, const T **data,
202 QArrayDataPointer *old)
203 {
204 const bool detach = needsDetach();
205 bool readjusted = false;
206 if (!detach) {
207 if (!n || (where == QArrayData::GrowsAtBeginning && freeSpaceAtBegin() >= n)
208 || (where == QArrayData::GrowsAtEnd && freeSpaceAtEnd() >= n))
209 return;
210 readjusted = tryReadjustFreeSpace(where, n, data);
211 Q_ASSERT(!readjusted
212 || (where == QArrayData::GrowsAtBeginning && freeSpaceAtBegin() >= n)
213 || (where == QArrayData::GrowsAtEnd && freeSpaceAtEnd() >= n));
214 }
215
216 if (!readjusted)
217 reallocateAndGrow(where, n, old);
218 }
219
220 /*! \internal
221
222 Reallocates to accommodate the free space for \a n elements at the
223 required side. The side is determined from \a pos. Might also shrink
224 when n < 0.
225 */
226 Q_NEVER_INLINE void reallocateAndGrow(QArrayData::GrowthPosition where, qsizetype n,
227 QArrayDataPointer *old = nullptr)
228 {
229 if constexpr (QTypeInfo<T>::isRelocatable && alignof(T) <= alignof(std::max_align_t)) {
230 if (where == QArrayData::GrowsAtEnd && !old && !needsDetach() && n > 0) {
231 (*this)->reallocate(constAllocatedCapacity() - freeSpaceAtEnd() + n, QArrayData::Grow); // fast path
232 return;
233 }
234 }
235
236 QArrayDataPointer dp(allocateGrow(*this, n, where));
237 if (n > 0)
238 Q_CHECK_PTR(dp.data());
239 if (where == QArrayData::GrowsAtBeginning) {
240 Q_ASSERT(dp.freeSpaceAtBegin() >= n);
241 } else {
242 Q_ASSERT(dp.freeSpaceAtEnd() >= n);
243 }
244 if (size) {
245 qsizetype toCopy = size;
246 if (n < 0)
247 toCopy += n;
248 if (needsDetach() || old)
249 dp->copyAppend(begin(), begin() + toCopy);
250 else
251 dp->moveAppend(begin(), begin() + toCopy);
252 Q_ASSERT(dp.size == toCopy);
253 }
254
255 swap(dp);
256 if (old)
257 old->swap(dp);
258 }
259
260 /*! \internal
261
262 Attempts to relocate [begin(), end()) to accommodate the free space for
263 \a n elements at the required side. The side is determined from \a pos.
264
265 Returns \c true if the internal data is moved. Returns \c false when
266 there is no point in moving the data or the move is impossible. If \c
267 false is returned, it is the responsibility of the caller to figure out
268 how to accommodate the free space for \a n elements at \a pos.
269
270 This function expects that certain preconditions are met, e.g. the
271 detach is not needed, n > 0 and so on. This is intentional to reduce the
272 number of if-statements when the caller knows that preconditions would
273 be satisfied.
274
275 \sa reallocateAndGrow
276 */
277 bool tryReadjustFreeSpace(QArrayData::GrowthPosition pos, qsizetype n, const T **data = nullptr)
278 {
279 Q_ASSERT(!this->needsDetach());
280 Q_ASSERT(n > 0);
281 Q_ASSERT((pos == QArrayData::GrowsAtEnd && this->freeSpaceAtEnd() < n)
282 || (pos == QArrayData::GrowsAtBeginning && this->freeSpaceAtBegin() < n));
283
284 const qsizetype capacity = this->constAllocatedCapacity();
285 const qsizetype freeAtBegin = this->freeSpaceAtBegin();
286 const qsizetype freeAtEnd = this->freeSpaceAtEnd();
287
288 qsizetype dataStartOffset = 0;
289 // algorithm:
290 // a. GrowsAtEnd: relocate if space at begin AND size < (capacity * 2) / 3
291 // [all goes to free space at end]:
292 // new free space at begin = 0
293 //
294 // b. GrowsAtBeginning: relocate if space at end AND size < capacity / 3
295 // [balance the free space]:
296 // new free space at begin = n + (total free space - n) / 2
297 if (pos == QArrayData::GrowsAtEnd && freeAtBegin >= n
298 && ((3 * this->size) < (2 * capacity))) {
299 // dataStartOffset = 0; - done in declaration
300 } else if (pos == QArrayData::GrowsAtBeginning && freeAtEnd >= n
301 && ((3 * this->size) < capacity)) {
302 // total free space == capacity - size
303 dataStartOffset = n + qMax(0, (capacity - this->size - n) / 2);
304 } else {
305 // nothing to do otherwise
306 return false;
307 }
308
309 relocate(dataStartOffset - freeAtBegin, data);
310
311 Q_ASSERT((pos == QArrayData::GrowsAtEnd && this->freeSpaceAtEnd() >= n)
312 || (pos == QArrayData::GrowsAtBeginning && this->freeSpaceAtBegin() >= n));
313 return true;
314 }
315
316 /*! \internal
317
318 Relocates [begin(), end()) by \a offset and updates \a data if it is not
319 \c nullptr and points into [begin(), end()).
320 */
321 void relocate(qsizetype offset, const T **data = nullptr)
322 {
323 T *res = this->ptr + offset;
324 QtPrivate::q_relocate_overlap_n(this->ptr, this->size, res);
325 // first update data pointer, then this->ptr
326 if (data && QtPrivate::q_points_into_range(*data, *this))
327 *data += offset;
328 this->ptr = res;
329 }
330
331 QArrayDataPointer sliced(qsizetype pos, qsizetype n) const &
332 {
333 QArrayDataPointer result(n);
334 std::uninitialized_copy_n(begin() + pos, n, result.begin());
335 result.size = n;
336 return result;
337 }
338
339 QArrayDataPointer sliced(qsizetype pos, qsizetype n) &&
340 {
341 if (needsDetach())
342 return sliced(pos, n);
343 T *newBeginning = begin() + pos;
344 std::destroy(begin(), newBeginning);
345 std::destroy(newBeginning + n, end());
346 setBegin(newBeginning);
347 size = n;
348 return std::move(*this);
349 }
350
351 void appendInitialize(qsizetype newSize)
352 {
353 Q_ASSERT(this->isMutable());
354 Q_ASSERT(!this->isShared());
355 Q_ASSERT(newSize > this->size);
356 Q_ASSERT(newSize - this->size <= this->freeSpaceAtEnd());
357
358 T *const b = this->begin() + this->size;
359 T *const e = this->begin() + newSize;
360 q17::uninitialized_value_construct(b, e);
361 this->size = newSize;
362 }
363
364 // forwards from QArrayData
365 constexpr qsizetype allocatedCapacity() noexcept { return d ? d->allocatedCapacity() : 0; }
366 constexpr
367 qsizetype constAllocatedCapacity() const noexcept { return d ? d->constAllocatedCapacity() : 0; }
368 constexpr void ref() noexcept { if (d) d->ref(); }
369 constexpr bool deref() noexcept { return !d || d->deref(); }
370 constexpr
371 bool isMutable() const noexcept { return d; } // Returns false if this object is fromRawData()
372 constexpr bool isShared() const noexcept { return !d || d->isShared(); }
373 constexpr
374 bool isSharedWith(const QArrayDataPointer &other) const noexcept { return d && d == other.d; }
375 constexpr bool needsDetach() const noexcept { return !d || d->needsDetach(); }
376 constexpr
377 qsizetype detachCapacity(qsizetype newSize) const noexcept { return d ? d->detachCapacity(newSize) : newSize; }
378 constexpr
379 const typename Data::ArrayOptions flags() const noexcept { return d ? d->flags : Data::ArrayOptionDefault; }
380 void setFlag(typename Data::ArrayOptions f) noexcept { Q_ASSERT(d); d->flags |= f; }
381 void clearFlag(typename Data::ArrayOptions f) noexcept { if (d) d->flags &= ~f; }
382
383 constexpr Data *d_ptr() noexcept { return d; }
384 constexpr void setBegin(T *begin) noexcept { ptr = begin; }
385
386 constexpr qsizetype freeSpaceAtBegin() const noexcept
387 {
388 if (d == nullptr)
389 return 0;
390 return this->ptr - Data::dataStart(d, alignof(typename Data::AlignmentDummy));
391 }
392
393 constexpr qsizetype freeSpaceAtEnd() const noexcept
394 {
395 if (d == nullptr)
396 return 0;
397 return d->constAllocatedCapacity() - freeSpaceAtBegin() - this->size;
398 }
399
400 // allocate and grow. Ensure that at the minimum requiredSpace is available at the requested end
401 static QArrayDataPointer allocateGrow(const QArrayDataPointer &from, qsizetype n, QArrayData::GrowthPosition position)
402 {
403 // calculate new capacity. We keep the free capacity at the side that does not have to grow
404 // to avoid quadratic behavior with mixed append/prepend cases
405
406 // use qMax below, because constAllocatedCapacity() can be 0 when using fromRawData()
407 qsizetype minimalCapacity = qMax(from.size, from.constAllocatedCapacity()) + n;
408 // subtract the free space at the side we want to allocate. This ensures that the total size requested is
409 // the existing allocation at the other side + size + n.
410 minimalCapacity -= (position == QArrayData::GrowsAtEnd) ? from.freeSpaceAtEnd() : from.freeSpaceAtBegin();
411 qsizetype capacity = from.detachCapacity(minimalCapacity);
412 const bool grows = capacity > from.constAllocatedCapacity();
413 auto [header, dataPtr] = Data::allocate(capacity, grows ? QArrayData::Grow : QArrayData::KeepSize);
414 const bool valid = header != nullptr && dataPtr != nullptr;
415 if (!valid)
416 return QArrayDataPointer(header, dataPtr);
417
418 // Idea: * when growing backwards, adjust pointer to prepare free space at the beginning
419 // * when growing forward, adjust by the previous data pointer offset
420 dataPtr += (position == QArrayData::GrowsAtBeginning)
421 ? n + qMax(0, (header->alloc - from.size - n) / 2)
422 : from.freeSpaceAtBegin();
423 header->flags = from.flags();
424 return QArrayDataPointer(header, dataPtr);
425 }
426
427 friend bool operator==(const QArrayDataPointer &lhs, const QArrayDataPointer &rhs) noexcept
428 {
429 return lhs.data() == rhs.data() && lhs.size == rhs.size;
430 }
431
432 friend bool operator!=(const QArrayDataPointer &lhs, const QArrayDataPointer &rhs) noexcept
433 {
434 return lhs.data() != rhs.data() || lhs.size != rhs.size;
435 }
436
437#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && !defined(QT_BOOTSTRAPPED)
438 Data *d = nullptr;
439#endif
440 T *ptr = nullptr;
441 qsizetype size = 0;
442#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED)
443 Data *d = nullptr;
444#endif
445};
446
447template <class T>
448constexpr void swap(QArrayDataPointer<T> &p1, QArrayDataPointer<T> &p2) noexcept
449{
450 p1.swap(p2);
451}
452
453template <class T>
455
456////////////////////////////////////////////////////////////////////////////////
457// Q_ARRAY_LITERAL
458
459// The idea here is to place a (read-only) copy of header and array data in an
460// mmappable portion of the executable (typically, .rodata section).
461
462// Hide array inside a lambda
463#define Q_ARRAY_LITERAL(Type, ...)
464 ([]() -> QArrayDataPointer<Type> {
465 static Type const data[] = { __VA_ARGS__ };
466 return QArrayDataPointer<Type>::fromRawData(const_cast<Type *>(data), std::size(data));
467 }())
468/**/
469
470QT_END_NAMESPACE
471
472#endif // include guard
constexpr void swap(QArrayDataPointer< T > &p1, QArrayDataPointer< T > &p2) noexcept
Q_DECLARE_TYPEINFO_BODY(QArrayDataPointer< T >, Q_RELOCATABLE_TYPE)