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