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