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