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