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