From b42a2b3c3338a320a438bc081cb885fd4547f01f Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 13 Jun 2012 18:22:27 +0200 Subject: [PATCH] Inline the size and begin pointer in QVector Add QGenericArray to simplify operations. This class can be shared by other tool classes. If there is nothing else to share it, we can move the code onto qvector.h. The one candidate is QList. All tests pass and valgrind is good. Change-Id: Ieaa80709caf5f50520aa97312ab726396f5475eb Reviewed-by: Simon Hausmann --- src/corelib/global/qt_pch.h | 2 +- src/corelib/text/qbytearraylist.h | 2 + src/corelib/tools/qarraydataops.h | 53 +- src/corelib/tools/qarraydatapointer.h | 2 +- src/corelib/tools/qvector.h | 1058 +++++++---------- src/corelib/tools/tools.pri | 1 - src/sql/kernel/qsqlindex.cpp | 3 - .../corelib/tools/qvector/tst_qvector.cpp | 8 +- .../other/toolsupport/tst_toolsupport.cpp | 8 +- 9 files changed, 462 insertions(+), 675 deletions(-) diff --git a/src/corelib/global/qt_pch.h b/src/corelib/global/qt_pch.h index 0dfd6c745f..1663d9392f 100644 --- a/src/corelib/global/qt_pch.h +++ b/src/corelib/global/qt_pch.h @@ -67,7 +67,7 @@ #include #include #include -#include /* All moc genereated code has this include */ +//#include /* All moc genereated code has this include */ #include #include #include diff --git a/src/corelib/text/qbytearraylist.h b/src/corelib/text/qbytearraylist.h index 4b6c926960..e113c6059c 100644 --- a/src/corelib/text/qbytearraylist.h +++ b/src/corelib/text/qbytearraylist.h @@ -80,8 +80,10 @@ public: inline QByteArray join(char sep) const { return QtPrivate::QByteArrayList_join(self(), &sep, 1); } +#if 0 inline int indexOf(const char *needle, int from = 0) const { return QtPrivate::QByteArrayList_indexOf(self(), needle, from); } +#endif private: typedef QVector Self; diff --git a/src/corelib/tools/qarraydataops.h b/src/corelib/tools/qarraydataops.h index dbd535fa3e..f3de6b65a9 100644 --- a/src/corelib/tools/qarraydataops.h +++ b/src/corelib/tools/qarraydataops.h @@ -150,7 +150,7 @@ struct QPodArrayOps this->size += (e - b); } - void insert(T *where, size_t n, T t) + void insert(T *where, size_t n, parameter_type t) { Q_ASSERT(!this->isShared()); Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end @@ -163,6 +163,19 @@ struct QPodArrayOps *where++ = t; } + void insert(T *where, T &&t) + { + Q_ASSERT(!this->isShared()); + Q_ASSERT(where >= this->begin() && where <= this->end()); + Q_ASSERT(this->allocatedCapacity() - this->size >= 1); + + ::memmove(static_cast(where + 1), static_cast(where), + (static_cast(this->end()) - where) * sizeof(T)); + this->size += 1; + new (where) T(std::move(t)); + } + + void erase(T *b, T *e) { Q_ASSERT(this->isMutable()); @@ -369,7 +382,7 @@ struct QGenericArrayOps } } - void insert(T *where, size_t n, T t) + void insert(T *where, size_t n, parameter_type t) { Q_ASSERT(!this->isShared()); Q_ASSERT(where >= this->begin() && where <= this->end()); @@ -431,6 +444,33 @@ struct QGenericArrayOps } } + void insert(T *where, T &&t) + { + Q_ASSERT(!this->isShared()); + Q_ASSERT(where >= this->begin() && where <= this->end()); + Q_ASSERT(this->allocatedCapacity() - this->size >= 1); + + // Array may be truncated at where in case of exceptions + T *const end = this->end(); + + if (where != end) { + // Move elements in array + T *readIter = end - 1; + T *writeIter = end; + new (writeIter) T(std::move(*readIter)); + while (readIter > where) { + --readIter; + --writeIter; + *writeIter = std::move(*readIter); + } + *where = std::move(t); + } else { + new (where) T(std::move(t)); + } + + ++this->size; + } + void erase(T *b, T *e) { Q_ASSERT(this->isMutable()); @@ -554,7 +594,7 @@ struct QMovableArrayOps this->size += (e - b); } - void insert(T *where, size_t n, T t) + void insert(T *where, size_t n, parameter_type t) { Q_ASSERT(!this->isShared()); Q_ASSERT(where >= this->begin() && where <= this->end()); @@ -618,6 +658,9 @@ struct QMovableArrayOps this->size += n; } + // use moving insert + using QGenericArrayOps::insert; + void erase(T *b, T *e) { Q_ASSERT(this->isMutable()); @@ -627,7 +670,7 @@ struct QMovableArrayOps struct Mover { - Mover(T *&start, const T *finish, uint &sz) + Mover(T *&start, const T *finish, int &sz) : destination(start) , source(start) , n(finish - start) @@ -644,7 +687,7 @@ struct QMovableArrayOps T *&destination; const T *const source; size_t n; - uint &size; + int &size; } mover(e, this->end(), this->size); // destroy the elements we're erasing diff --git a/src/corelib/tools/qarraydatapointer.h b/src/corelib/tools/qarraydatapointer.h index 9103064bd9..3d850c0144 100644 --- a/src/corelib/tools/qarraydatapointer.h +++ b/src/corelib/tools/qarraydatapointer.h @@ -215,7 +215,7 @@ protected: T *ptr; public: - uint size; + int size; }; template diff --git a/src/corelib/tools/qvector.h b/src/corelib/tools/qvector.h index 330bf8bb98..640809cd14 100644 --- a/src/corelib/tools/qvector.h +++ b/src/corelib/tools/qvector.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Intel Corporation ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -40,22 +41,14 @@ #ifndef QVECTOR_H #define QVECTOR_H -#include -#include -#include -#include +#include +#include #include -#include +#include -#include +#include +#include #include -#if QT_VERSION < QT_VERSION_CHECK(6,0,0) -#include -#endif -#include -#include - -#include QT_BEGIN_NAMESPACE @@ -79,85 +72,233 @@ class QVector #endif { typedef QTypedArrayData Data; - Data *d; + typedef QArrayDataOps DataOps; + typedef QArrayDataPointer DataPointer; + + DataPointer d; template friend int QtPrivate::indexOf(const QVector &list, const U &u, int from); template friend int QtPrivate::lastIndexOf(const QVector &list, const U &u, int from); public: - inline QVector() noexcept : d(Data::sharedNull()) { } - explicit QVector(int size); - QVector(int size, const T &t); - inline QVector(const QVector &v); - inline ~QVector() { if (!d->deref()) freeData(d); } - QVector &operator=(const QVector &v); - QVector(QVector &&other) noexcept : d(other.d) { other.d = Data::sharedNull(); } - QVector &operator=(QVector &&other) noexcept - { QVector moved(std::move(other)); swap(moved); return *this; } - void swap(QVector &other) noexcept { qSwap(d, other.d); } - inline QVector(std::initializer_list args); - QVector &operator=(std::initializer_list args); - template = true> - inline QVector(InputIterator first, InputIterator last); - explicit QVector(QArrayDataPointerRef ref) noexcept : d(ref.ptr) {} + typedef T Type; + typedef T value_type; + typedef value_type *pointer; + typedef const value_type *const_pointer; + typedef value_type &reference; + typedef const value_type &const_reference; + typedef int size_type; + typedef qptrdiff difference_type; + typedef typename Data::iterator iterator; + typedef typename Data::const_iterator const_iterator; + typedef iterator Iterator; + typedef const_iterator ConstIterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + // ### The line below triggers too earely instantiation of QTypeInfo in qvariant.h + //typedef typename DataOps::parameter_type parameter_type; + typedef const_reference parameter_type; - bool operator==(const QVector &v) const; - inline bool operator!=(const QVector &v) const { return !(*this == v); } - - inline int size() const { return d->size; } - - inline bool isEmpty() const { return d->size == 0; } - - void resize(int size); - - inline int capacity() const { return d->constAllocatedCapacity(); } - void reserve(int size); - inline void squeeze() +private: + void resize_internal(int i, Qt::Initialization); + bool isValidIterator(const_iterator i) const + { + const std::less less = {}; + return !less(d->end(), i) && !less(i, d->begin()); + } + QVector(DataPointer dd) noexcept + : d(dd) { - if (d->size < int(d->allocatedCapacity())) { - if (!d->size) { - *this = QVector(); - return; - } - realloc(d->size, d->detachFlags()); - } } - inline void detach(); - inline bool isDetached() const { return !d->isShared(); } +public: + inline QVector() noexcept { } + explicit QVector(int size) + : d(Data::allocate(size)) + { + if (size) + d->appendInitialize(size); + } + QVector(int size, const T &t) + : d(Data::allocate(size)) + { + if (size) + d->copyAppend(size, t); + } + + inline QVector(const QVector &other) noexcept : d(other.d) {} + QVector(QVector &&other) noexcept : d(std::move(other.d)) {} + inline QVector(std::initializer_list args) + : d(Data::allocate(args.size())) + { + if (args.size()) + d->copyAppend(args.begin(), args.end()); + } + + ~QVector() /*noexcept(std::is_nothrow_destructible::value)*/ {} + QVector &operator=(const QVector &other) { d = other.d; return *this; } + QVector &operator=(QVector &&other) noexcept(std::is_nothrow_destructible::value) + { + d = std::move(other.d); + return *this; + } + QVector &operator=(std::initializer_list args) + { + d = DataPointer(Data::allocate(args.size())); + if (args.size()) + d->copyAppend(args.begin(), args.end()); + return *this; + } + template = true> + QVector(InputIterator i1, InputIterator i2) + : d(Data::allocate(std::distance(i1, i2))) + { + if (std::distance(i1, i2)) + d->copyAppend(i1, i2); + } + + template = true> + QVector(InputIterator i1, InputIterator i2) + : QVector() + { + QtPrivate::reserveIfForwardIterator(this, i1, i2); + std::copy(i1, i2, std::back_inserter(*this)); + } + + void swap(QVector &other) noexcept { qSwap(d, other.d); } + + friend bool operator==(const QVector &l, const QVector &r) + { + if (l.size() != r.size()) + return false; + if (l.begin() == r.begin()) + return true; + + // do element-by-element comparison + return l.d->compare(l.begin(), r.begin(), l.size()); + } + friend bool operator!=(const QVector &l, const QVector &r) + { + return !(l == r); + } + + int size() const noexcept { return int(d->size); } + int count() const noexcept { return size(); } + int length() const noexcept { return size(); } + + inline bool isEmpty() const noexcept { return d->size == 0; } + + void resize(int size) + { + resize_internal(size, Qt::Uninitialized); + if (size > this->size()) + d->appendInitialize(size); + } + void resize(int size, parameter_type c) + { + resize_internal(size, Qt::Uninitialized); + if (size > this->size()) + d->copyAppend(size - this->size(), c); + } + + inline int capacity() const { return int(d->constAllocatedCapacity()); } + void reserve(int size); + inline void squeeze(); + + void detach() { d.detach(); } + bool isDetached() const noexcept { return !d->isShared(); } inline bool isSharedWith(const QVector &other) const { return d == other.d; } - inline T *data() { detach(); return d->begin(); } - inline const T *data() const { return d->begin(); } - inline const T *constData() const { return d->begin(); } - void clear(); + pointer data() { detach(); return d->data(); } + const_pointer data() const noexcept { return d->data(); } + const_pointer constData() const noexcept { return d->data(); } + void clear() { + if (!size()) + return; + if (d->needsDetach()) { + // must allocate memory + DataPointer detached(Data::allocate(d.allocatedCapacity(), d->detachFlags())); + d.swap(detached); + } else { + d->truncate(0); + } + } - const T &at(int i) const; - T &operator[](int i); - const T &operator[](int i) const; - void append(const T &t); - void append(T &&t); - inline void append(const QVector &l) { *this += l; } + const_reference at(int i) const noexcept + { + Q_ASSERT_X(size_t(i) < size_t(d->size), "QVector::at", "index out of range"); + return data()[i]; + } + reference operator[](int i) + { + Q_ASSERT_X(size_t(i) < size_t(d->size), "QVector::operator[]", "index out of range"); + detach(); + return data()[i]; + } + const_reference operator[](int i) const noexcept { return at(i); } + void append(const_reference t) + { append(const_iterator(std::addressof(t)), const_iterator(std::addressof(t)) + 1); } + void append(const_iterator i1, const_iterator i2); + void append(value_type &&t); + void append(const QVector &l) { append(l.constBegin(), l.constEnd()); } void prepend(T &&t); void prepend(const T &t); - void insert(int i, T &&t); - void insert(int i, const T &t); - void insert(int i, int n, const T &t); - void replace(int i, const T &t); - void remove(int i); - void remove(int i, int n); - inline void removeFirst() { Q_ASSERT(!isEmpty()); erase(d->begin()); } - inline void removeLast(); - T takeFirst() { Q_ASSERT(!isEmpty()); T r = std::move(first()); removeFirst(); return r; } - T takeLast() { Q_ASSERT(!isEmpty()); T r = std::move(last()); removeLast(); return r; } + iterator insert(int i, parameter_type t) + { return insert(i, 1, t); } + iterator insert(int i, int n, parameter_type t); + iterator insert(const_iterator before, parameter_type t) + { + Q_ASSERT_X(isValidIterator(before), "QVector::insert", "The specified iterator argument 'before' is invalid"); + return insert(before, 1, t); + } + iterator insert(const_iterator before, int n, parameter_type t) + { + Q_ASSERT_X(isValidIterator(before), "QVector::insert", "The specified iterator argument 'before' is invalid"); + return insert(std::distance(constBegin(), before), n, t); + } + inline iterator insert(const_iterator before, T &&t) + { + Q_ASSERT_X(isValidIterator(before), "QVector::insert", "The specified iterator argument 'before' is invalid"); + return insert(std::distance(constBegin(), before), std::move(t)); + } + iterator insert(int i, T &&t); +#if 0 + template< class InputIt > + iterator insert( const_iterator pos, InputIt first, InputIt last ); + iterator insert( const_iterator pos, std::initializer_list ilist ); +#endif + void replace(int i, const T &t) + { + Q_ASSERT_X(i >= 0 && i < d->size, "QVector::replace", "index out of range"); + const T copy(t); + data()[i] = copy; + } + void replace(int i, T &&t) + { + Q_ASSERT_X(i >= 0 && i < d->size, "QVector::replace", "index out of range"); + const T copy(std::move(t)); + data()[i] = std::move(copy); + } - QVector &fill(const T &t, int size = -1); + void remove(int i, int n = 1); + void removeFirst() { Q_ASSERT(!isEmpty()); remove(0); } + void removeLast() { Q_ASSERT(!isEmpty()); remove(size() - 1); } + value_type takeFirst() { Q_ASSERT(!isEmpty()); value_type v = std::move(first()); remove(0); return v; } + value_type takeLast() { Q_ASSERT(!isEmpty()); value_type v = std::move(last()); remove(size() - 1); return v; } - int indexOf(const T &t, int from = 0) const; - int lastIndexOf(const T &t, int from = -1) const; - bool contains(const T &t) const; - int count(const T &t) const; + QVector &fill(parameter_type t, int size = -1); + + int indexOf(const T &t, int from = 0) const noexcept; + int lastIndexOf(const T &t, int from = -1) const noexcept; + bool contains(const T &t) const noexcept + { + return indexOf(t) != -1; + } + int count(const T &t) const noexcept + { + return int(std::count(&*cbegin(), &*cend(), t)); + } // QList compatibility void removeAt(int i) { remove(i); } @@ -166,10 +307,10 @@ public: const const_iterator ce = this->cend(), cit = std::find(this->cbegin(), ce, t); if (cit == ce) return 0; + int index = cit - this->cbegin(); // next operation detaches, so ce, cit, t may become invalidated: const T tCopy = t; - const int firstFoundIdx = std::distance(this->cbegin(), cit); - const iterator e = end(), it = std::remove(begin() + firstFoundIdx, e, tCopy); + const iterator e = end(), it = std::remove(begin() + index, e, tCopy); const int result = std::distance(it, e); erase(it, e); return result; @@ -182,7 +323,6 @@ public: remove(i); return true; } - int length() const { return size(); } T takeAt(int i) { T t = std::move((*this)[i]); remove(i); return t; } void move(int from, int to) { @@ -199,32 +339,26 @@ public: } // STL-style - typedef typename Data::iterator iterator; - typedef typename Data::const_iterator const_iterator; - typedef std::reverse_iterator reverse_iterator; - typedef std::reverse_iterator const_reverse_iterator; - inline iterator begin() { detach(); return d->begin(); } - inline const_iterator begin() const noexcept { return d->constBegin(); } - inline const_iterator cbegin() const noexcept { return d->constBegin(); } - inline const_iterator constBegin() const noexcept { return d->constBegin(); } - inline iterator end() { detach(); return d->end(); } - inline const_iterator end() const noexcept { return d->constEnd(); } - inline const_iterator cend() const noexcept { return d->constEnd(); } - inline const_iterator constEnd() const noexcept { return d->constEnd(); } + iterator begin() { detach(); return d->begin(); } + iterator end() { detach(); return d->end(); } + + const_iterator begin() const noexcept { return d->constBegin(); } + const_iterator end() const noexcept { return d->constEnd(); } + const_iterator cbegin() const noexcept { return d->constBegin(); } + const_iterator cend() const noexcept { return d->constEnd(); } + const_iterator constBegin() const noexcept { return d->constBegin(); } + const_iterator constEnd() const noexcept { return d->constEnd(); } reverse_iterator rbegin() { return reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); } const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); } - iterator insert(iterator before, int n, const T &x); - inline iterator insert(iterator before, const T &x) { return insert(before, 1, x); } - inline iterator insert(iterator before, T &&x); + iterator erase(iterator begin, iterator end); inline iterator erase(iterator pos) { return erase(pos, pos+1); } // more Qt - inline int count() const { return d->size; } inline T& first() { Q_ASSERT(!isEmpty()); return *begin(); } inline const T &first() const { Q_ASSERT(!isEmpty()); return *begin(); } inline const T &constFirst() const { Q_ASSERT(!isEmpty()); return *begin(); } @@ -235,7 +369,7 @@ public: inline bool endsWith(const T &t) const { return !isEmpty() && last() == t; } QVector mid(int pos, int len = -1) const; - T value(int i) const; + T value(int i) const { return value(i, T()); } T value(int i, const T &defaultValue) const; void swapItemsAt(int i, int j) { @@ -246,15 +380,6 @@ public: } // STL compatibility - typedef T value_type; - typedef value_type* pointer; - typedef const value_type* const_pointer; - typedef value_type& reference; - typedef const value_type& const_reference; - typedef qptrdiff difference_type; - typedef iterator Iterator; - typedef const_iterator ConstIterator; - typedef int size_type; inline void push_back(const T &t) { append(t); } void push_back(T &&t) { append(std::move(t)); } void push_front(T &&t) { prepend(std::move(t)); } @@ -263,14 +388,14 @@ public: void pop_front() { removeFirst(); } inline bool empty() const { return d->size == 0; } - inline T& front() { return first(); } + inline reference front() { return first(); } inline const_reference front() const { return first(); } inline reference back() { return last(); } inline const_reference back() const { return last(); } void shrink_to_fit() { squeeze(); } // comfort - QVector &operator+=(const QVector &l); + QVector &operator+=(const QVector &l) { append(l.cbegin(), l.cend()); return *this; } inline QVector operator+(const QVector &l) const { QVector n = *this; n += l; return n; } inline QVector &operator+=(const T &t) @@ -284,34 +409,12 @@ public: inline QVector &operator<<(T &&t) { append(std::move(t)); return *this; } -#if QT_VERSION < QT_VERSION_CHECK(6,0,0) - Q_DECL_DEPRECATED_X("Use QVector(vector.begin(), vector.end()) instead.") - static inline QVector fromStdVector(const std::vector &vector) - { return QVector(vector.begin(), vector.end()); } - Q_DECL_DEPRECATED_X("Use std::vector(vector.begin(), vector.end()) instead.") - inline std::vector toStdVector() const - { return std::vector(d->begin(), d->end()); } -#endif - // Consider deprecating in 6.4 or later static QVector fromList(const QVector &list) { return list; } QVector toList() const { return *this; } static inline QVector fromVector(const QVector &vector) { return vector; } inline QVector toVector() const { return *this; } - -private: - void realloc(int alloc, QArrayData::ArrayOptions options); - void freeData(Data *d); - void defaultConstruct(T *from, T *to); - void copyConstruct(const T *srcFrom, const T *srcTo, T *dstFrom); - void destruct(T *from, T *to); - bool isValidIterator(const iterator &i) const - { - const std::less less = {}; - return !less(d->end(), i) && !less(i, d->begin()); - } - class AlignmentDummy { Data header; T array[1]; }; }; #if defined(__cpp_deduction_guides) && __cpp_deduction_guides >= 201606 @@ -321,153 +424,86 @@ template QVector; #endif -#ifdef Q_CC_MSVC -// behavior change: an object of POD type constructed with an initializer of the form () -// will be default-initialized -# pragma warning ( push ) -# pragma warning ( disable : 4345 ) -# pragma warning(disable : 4127) // conditional expression is constant -#endif - template -void QVector::defaultConstruct(T *from, T *to) +inline void QVector::resize_internal(int newSize, Qt::Initialization) { - if (QTypeInfo::isComplex) { - while (from != to) { - new (from++) T(); + Q_ASSERT(newSize >= 0); + + if (d->needsDetach() || newSize > capacity()) { + // must allocate memory + DataPointer detached(Data::allocate(d->detachCapacity(newSize), + d->detachFlags())); + if (size() && newSize) { + detached->copyAppend(constBegin(), constBegin() + qMin(newSize, size())); } - } else { - ::memset(static_cast(from), 0, (to - from) * sizeof(T)); + d.swap(detached); } -} -template -void QVector::copyConstruct(const T *srcFrom, const T *srcTo, T *dstFrom) -{ - if (QTypeInfo::isComplex) { - while (srcFrom != srcTo) - new (dstFrom++) T(*srcFrom++); - } else { - ::memcpy(static_cast(dstFrom), static_cast(srcFrom), (srcTo - srcFrom) * sizeof(T)); - } -} - -template -void QVector::destruct(T *from, T *to) -{ - if (QTypeInfo::isComplex) { - while (from != to) { - from++->~T(); - } - } -} - -template -inline QVector::QVector(const QVector &v) -{ - if (v.d->ref()) { - d = v.d; - } else { - if (v.d->flags & Data::CapacityReserved) { - d = Data::allocate(v.d->allocatedCapacity()).first; - Q_CHECK_PTR(d); - d->flags |= Data::CapacityReserved; - } else { - d = Data::allocate(v.d->size).first; - Q_CHECK_PTR(d); - } - if (v.d->size) { - copyConstruct(v.d->begin(), v.d->end(), d->begin()); - d->size = v.d->size; - } - } -} - -#if defined(Q_CC_MSVC) -#pragma warning( pop ) -#endif - -template -void QVector::detach() -{ - // ### check whether this is still required - if (d->isStatic()) - return; - - if (d->needsDetach()) { - realloc(d->allocatedCapacity(), d->detachFlags()); - Q_ASSERT(isDetached()); - } + if (newSize < size()) + d->truncate(newSize); } template void QVector::reserve(int asize) { - if (asize > int(d->allocatedCapacity())) - realloc(asize, typename Data::ArrayOptions(d->flags | Data::CapacityReserved)); - else if (isDetached()) - d->flags |= Data::CapacityReserved; - Q_ASSERT(int(d->allocatedCapacity()) >= asize); + // capacity() == 0 for immutable data, so this will force a detaching below + if (asize <= capacity()) { + if (d->flags() & Data::CapacityReserved) + return; // already reserved, don't shrink + if (!d->isShared()) { + // accept current allocation, don't shrink + d->flags() |= Data::CapacityReserved; + return; + } + } + + DataPointer detached(Data::allocate(qMax(asize, size()), + d->detachFlags() | Data::CapacityReserved)); + detached->copyAppend(constBegin(), constEnd()); + d.swap(detached); } template -void QVector::resize(int asize) +inline void QVector::squeeze() { - if (asize == d->size) - return detach(); - int oldAlloc = d->allocatedCapacity(); - if (asize > oldAlloc || !isDetached()) { // there is not enough space - QArrayData::ArrayOptions opt = d->detachFlags(); - if (asize > oldAlloc) - opt |= QArrayData::GrowsForward; - realloc(qMax(oldAlloc, asize), opt); + if (d->needsDetach() || size() != capacity()) { + // must allocate memory + DataPointer detached(Data::allocate(size(), d->detachFlags() & ~Data::CapacityReserved)); + if (size()) { + detached->copyAppend(constBegin(), constEnd()); + } + d.swap(detached); } - if (asize < d->size) - destruct(begin() + asize, end()); - else - defaultConstruct(end(), begin() + asize); - d->size = asize; } -template -inline void QVector::clear() -{ - if (!d->size) - return; - destruct(begin(), end()); - d->size = 0; -} -template -inline const T &QVector::at(int i) const -{ Q_ASSERT_X(i >= 0 && i < d->size, "QVector::at", "index out of range"); - return d->begin()[i]; } -template -inline const T &QVector::operator[](int i) const -{ Q_ASSERT_X(i >= 0 && i < d->size, "QVector::operator[]", "index out of range"); - return d->begin()[i]; } -template -inline T &QVector::operator[](int i) -{ Q_ASSERT_X(i >= 0 && i < d->size, "QVector::operator[]", "index out of range"); - return data()[i]; } -template -inline void QVector::insert(int i, const T &t) -{ Q_ASSERT_X(i >= 0 && i <= d->size, "QVector::insert", "index out of range"); - insert(begin() + i, 1, t); } -template -inline void QVector::insert(int i, int n, const T &t) -{ Q_ASSERT_X(i >= 0 && i <= d->size, "QVector::insert", "index out of range"); - insert(begin() + i, n, t); } -template -inline void QVector::insert(int i, T &&t) -{ Q_ASSERT_X(i >= 0 && i <= d->size, "QVector::insert", "index out of range"); - insert(begin() + i, std::move(t)); } + template inline void QVector::remove(int i, int n) -{ Q_ASSERT_X(i >= 0 && n >= 0 && i + n <= d->size, "QVector::remove", "index out of range"); - erase(d->begin() + i, d->begin() + i + n); } -template -inline void QVector::remove(int i) -{ Q_ASSERT_X(i >= 0 && i < d->size, "QVector::remove", "index out of range"); - erase(d->begin() + i, d->begin() + i + 1); } +{ + Q_ASSERT_X(size_t(i) + size_t(n) <= size_t(d->size), "QVector::remove", "index out of range"); + Q_ASSERT_X(n >= 0, "QVector::remove", "invalid count"); + + if (n == 0) + return; + + const size_t newSize = size() - n; + if (d->needsDetach() || + ((d->flags() & Data::CapacityReserved) == 0 + && newSize < d->allocatedCapacity()/2)) { + // allocate memory + DataPointer detached(Data::allocate(d->detachCapacity(newSize), + d->detachFlags() & ~(Data::GrowsBackwards | Data::GrowsForward))); + const_iterator where = constBegin() + i; + if (newSize) { + detached->copyAppend(constBegin(), where); + detached->copyAppend(where + n, constEnd()); + } + d.swap(detached); + } else { + // we're detached and we can just move data around + d->erase(d->begin() + i, d->begin() + i + n); + } +} + template inline void QVector::prepend(const T &t) { insert(begin(), 1, t); } @@ -475,304 +511,106 @@ template inline void QVector::prepend(T &&t) { insert(begin(), std::move(t)); } -template -inline void QVector::replace(int i, const T &t) -{ - Q_ASSERT_X(i >= 0 && i < d->size, "QVector::replace", "index out of range"); - const T copy(t); - data()[i] = copy; -} - -template -QVector &QVector::operator=(const QVector &v) -{ - if (v.d != d) { - QVector tmp(v); - tmp.swap(*this); - } - return *this; -} - -template -QVector::QVector(int asize) -{ - Q_ASSERT_X(asize >= 0, "QVector::QVector", "Size must be greater than or equal to 0."); - if (Q_LIKELY(asize > 0)) { - d = Data::allocate(asize).first; - Q_CHECK_PTR(d); - d->size = asize; - defaultConstruct(d->begin(), d->end()); - } else { - d = Data::sharedNull(); - } -} - -template -QVector::QVector(int asize, const T &t) -{ - Q_ASSERT_X(asize >= 0, "QVector::QVector", "Size must be greater than or equal to 0."); - if (asize > 0) { - d = Data::allocate(asize).first; - Q_CHECK_PTR(d); - d->size = asize; - T* i = d->end(); - while (i != d->begin()) - new (--i) T(t); - } else { - d = Data::sharedNull(); - } -} - -#if defined(Q_CC_MSVC) -QT_WARNING_PUSH -QT_WARNING_DISABLE_MSVC(4127) // conditional expression is constant -#endif // Q_CC_MSVC - -template -QVector::QVector(std::initializer_list args) -{ - if (args.size() > 0) { - d = Data::allocate(args.size()).first; - Q_CHECK_PTR(d); - // std::initializer_list::iterator is guaranteed to be - // const T* ([support.initlist]/1), so can be memcpy'ed away from by copyConstruct - copyConstruct(args.begin(), args.end(), d->begin()); - d->size = int(args.size()); - } else { - d = Data::sharedNull(); - } -} - -template -QVector &QVector::operator=(std::initializer_list args) -{ - QVector tmp(args); - tmp.swap(*this); - return *this; -} - -#if defined(Q_CC_MSVC) -QT_WARNING_POP -#endif // Q_CC_MSVC - -template -template > -QVector::QVector(InputIterator first, InputIterator last) - : QVector() -{ - QtPrivate::reserveIfForwardIterator(this, first, last); - std::copy(first, last, std::back_inserter(*this)); -} - -template -void QVector::freeData(Data *x) -{ - destruct(x->begin(), x->end()); - Data::deallocate(x); -} - -#if defined(Q_CC_MSVC) -QT_WARNING_PUSH -QT_WARNING_DISABLE_MSVC(4127) // conditional expression is constant -#endif - template -void QVector::realloc(int aalloc, QArrayData::ArrayOptions options) +inline T QVector::value(int i, const T &defaultValue) const { - Q_ASSERT(aalloc >= d->size); - Data *x = d; - - const bool isShared = d->isShared(); - - QT_TRY { - // allocate memory - auto pair = Data::allocate(aalloc, options); - x = pair.first; - Q_CHECK_PTR(x); - // aalloc is bigger then 0 so it is not [un]sharedEmpty - Q_ASSERT(!x->isStatic()); - x->size = d->size; - - T *srcBegin = d->begin(); - T *srcEnd = d->end(); - T *dst = x->begin(); - - if (!QTypeInfoQuery::isRelocatable || (isShared && QTypeInfo::isComplex)) { - QT_TRY { - if (isShared || !std::is_nothrow_move_constructible::value) { - // we can not move the data, we need to copy construct it - while (srcBegin != srcEnd) - new (dst++) T(*srcBegin++); - } else { - while (srcBegin != srcEnd) - new (dst++) T(std::move(*srcBegin++)); - } - } QT_CATCH (...) { - // destruct already copied objects - destruct(x->begin(), dst); - QT_RETHROW; - } - } else { - ::memcpy(static_cast(dst), static_cast(srcBegin), (srcEnd - srcBegin) * sizeof(T)); - dst += srcEnd - srcBegin; - } - - } QT_CATCH (...) { - Data::deallocate(x); - QT_RETHROW; - } - - Q_ASSERT(d != x); - if (!d->deref()) { - if (!QTypeInfoQuery::isRelocatable || !aalloc || (isShared && QTypeInfo::isComplex)) { - // data was copy constructed, we need to call destructors - // or if !alloc we did nothing to the old 'd'. - freeData(d); - } else { - Data::deallocate(d); - } - } - d = x; - - Q_ASSERT(d->data()); - Q_ASSERT(uint(d->size) <= d->allocatedCapacity()); - Q_ASSERT(d != Data::sharedNull()); - Q_ASSERT(d->allocatedCapacity() >= uint(aalloc)); -} - -#if defined(Q_CC_MSVC) -QT_WARNING_POP -#endif - -template -Q_OUTOFLINE_TEMPLATE T QVector::value(int i) const -{ - if (uint(i) >= uint(d->size)) { - return T(); - } - return d->begin()[i]; -} -template -Q_OUTOFLINE_TEMPLATE T QVector::value(int i, const T &defaultValue) const -{ - return uint(i) >= uint(d->size) ? defaultValue : d->begin()[i]; + return size_t(i) < size_t(d->size) ? at(i) : defaultValue; } template -void QVector::append(const T &t) +inline void QVector::append(const_iterator i1, const_iterator i2) { - const bool isTooSmall = d->size >= int(d->allocatedCapacity()); - QArrayData::ArrayOptions opt = d->detachFlags(); - if (!isDetached() || isTooSmall) { - T copy(t); + if (i1 == i2) + return; + const size_t newSize = size() + std::distance(i1, i2); + if (d->needsDetach() || newSize > d->allocatedCapacity()) { + DataPointer detached(Data::allocate(d->detachCapacity(newSize), + d->detachFlags() | Data::GrowsForward)); + detached->copyAppend(constBegin(), constEnd()); + detached->copyAppend(i1, i2); + d.swap(detached); + } else { + // we're detached and we can just move data around + d->copyAppend(i1, i2); + } +} + +template +inline void QVector::append(value_type &&t) +{ + const size_t newSize = size() + 1; + const bool isTooSmall = newSize > d->allocatedCapacity(); + const bool isOverlapping = std::addressof(*d->begin()) <= std::addressof(t) + && std::addressof(t) < std::addressof(*d->end()); + if (isTooSmall || d->needsDetach() || Q_UNLIKELY(isOverlapping)) { + typename Data::ArrayOptions flags = d->detachFlags(); if (isTooSmall) - opt |= QArrayData::GrowsForward; - realloc(isTooSmall ? d->size + 1 : d->allocatedCapacity(), opt); - - if (QTypeInfo::isComplex) - new (d->end()) T(std::move(copy)); - else - *d->end() = std::move(copy); - + flags |= Data::GrowsForward; + DataPointer detached(Data::allocate(d->detachCapacity(newSize), flags)); + detached->copyAppend(constBegin(), constEnd()); + detached->moveAppend(std::addressof(t), std::addressof(t) + 1); + d.swap(detached); } else { - if (QTypeInfo::isComplex) - new (d->end()) T(t); - else - *d->end() = t; + // we're detached and we can just move data around + d->moveAppend(std::addressof(t), std::addressof(t) + 1); } - ++d->size; } template -void QVector::append(T &&t) +inline typename QVector::iterator +QVector::insert(int i, int n, parameter_type t) { - const bool isTooSmall = uint(d->size + 1) > d->allocatedCapacity(); - if (!isDetached() || isTooSmall) { - QArrayData::ArrayOptions opt(isTooSmall ? QArrayData::GrowsForward : QArrayData::DefaultAllocationFlags); - realloc(isTooSmall ? d->size + 1 : d->allocatedCapacity(), opt); - } + Q_ASSERT_X(size_t(i) <= size_t(d->size), "QVector::insert", "index out of range"); - new (d->end()) T(std::move(t)); + // we don't have a quick exit for n == 0 + // it's not worth wasting CPU cycles for that - ++d->size; -} + const size_t newSize = size() + n; + if (d->needsDetach() || newSize > d->allocatedCapacity()) { + typename Data::ArrayOptions flags = d->detachFlags() | Data::GrowsForward; + if (size_t(i) <= newSize / 4) + flags |= Data::GrowsBackwards; -template -void QVector::removeLast() -{ - Q_ASSERT(!isEmpty()); - Q_ASSERT(d->allocatedCapacity()); - - if (d->needsDetach()) - detach(); - --d->size; - if (QTypeInfo::isComplex) - (d->data() + d->size)->~T(); -} - -template -typename QVector::iterator QVector::insert(iterator before, size_type n, const T &t) -{ - Q_ASSERT_X(isValidIterator(before), "QVector::insert", "The specified iterator argument 'before' is invalid"); - - const auto offset = std::distance(d->begin(), before); - if (n != 0) { - const T copy(t); - if (!isDetached() || d->size + n > int(d->allocatedCapacity())) - realloc(d->size + n, QArrayData::GrowsForward); - if (!QTypeInfoQuery::isRelocatable) { - T *b = d->end(); - T *i = d->end() + n; - while (i != b) - new (--i) T; - i = d->end(); - T *j = i + n; - b = d->begin() + offset; - while (i != b) - *--j = *--i; - i = b+n; - while (i != b) - *--i = copy; - } else { - T *b = d->begin() + offset; - T *i = b + n; - memmove(static_cast(i), static_cast(b), (d->size - offset) * sizeof(T)); - while (i != b) - new (--i) T(copy); - } - d->size += n; - } - return d->begin() + offset; -} - -template -typename QVector::iterator QVector::insert(iterator before, T &&t) -{ - Q_ASSERT_X(isValidIterator(before), "QVector::insert", "The specified iterator argument 'before' is invalid"); - - const auto offset = std::distance(d->begin(), before); - if (!isDetached() || d->size + 1 > int(d->allocatedCapacity())) - realloc(d->size + 1, QArrayData::GrowsForward); - if (!QTypeInfoQuery::isRelocatable) { - T *i = d->end(); - T *j = i + 1; - T *b = d->begin() + offset; - // The new end-element needs to be constructed, the rest must be move assigned - if (i != b) { - new (--j) T(std::move(*--i)); - while (i != b) - *--j = std::move(*--i); - *b = std::move(t); - } else { - new (b) T(std::move(t)); - } + DataPointer detached(Data::allocate(d->detachCapacity(newSize), flags)); + const_iterator where = constBegin() + i; + detached->copyAppend(constBegin(), where); + detached->copyAppend(n, t); + detached->copyAppend(where, constEnd()); + d.swap(detached); } else { - T *b = d->begin() + offset; - memmove(static_cast(b + 1), static_cast(b), (d->size - offset) * sizeof(T)); - new (b) T(std::move(t)); + // we're detached and we can just move data around + if (i == size()) { + d->copyAppend(n, t); + } else { + T copy(t); + d->insert(d.begin() + i, n, copy); + } } - d->size += 1; - return d->begin() + offset; + return d.begin() + i; +} + +template +inline typename QVector::iterator +QVector::insert(int i, T &&t) +{ + Q_ASSERT_X(size_t(i) <= size_t(d->size), "QVector::insert", "index out of range"); + + const size_t newSize = size() + 1; + if (d->needsDetach() || newSize > d->allocatedCapacity()) { + typename Data::ArrayOptions flags = d->detachFlags() | Data::GrowsForward; + if (size_t(i) <= newSize / 4) + flags |= Data::GrowsBackwards; + + DataPointer detached(Data::allocate(d->detachCapacity(newSize), flags)); + const_iterator where = constBegin() + i; + detached->copyAppend(constBegin(), where); + detached->moveAppend(std::addressof(t), std::addressof(t) + 1); + detached->copyAppend(where, constEnd()); + d.swap(detached); + } else { + d->insert(d.begin() + i, std::move(t)); + } + return d.begin() + i; } template @@ -780,102 +618,33 @@ typename QVector::iterator QVector::erase(iterator abegin, iterator aend) { Q_ASSERT_X(isValidIterator(abegin), "QVector::erase", "The specified iterator argument 'abegin' is invalid"); Q_ASSERT_X(isValidIterator(aend), "QVector::erase", "The specified iterator argument 'aend' is invalid"); + Q_ASSERT(aend >= abegin); - const auto itemsToErase = aend - abegin; + // d.begin() so we don't detach just yet + int i = std::distance(d.begin(), abegin); + int n = std::distance(abegin, aend); + remove(i, n); - if (!itemsToErase) - return abegin; - - Q_ASSERT(abegin >= d->begin()); - Q_ASSERT(aend <= d->end()); - Q_ASSERT(abegin <= aend); - - const auto itemsUntouched = abegin - d->begin(); - - // FIXME we could do a proper realloc, which copy constructs only needed data. - // FIXME we are about to delete data - maybe it is good time to shrink? - // FIXME the shrink is also an issue in removeLast, that is just a copy + reduce of this. - if (d->allocatedCapacity()) { - detach(); - abegin = d->begin() + itemsUntouched; - aend = abegin + itemsToErase; - if (!QTypeInfoQuery::isRelocatable) { - iterator moveBegin = abegin + itemsToErase; - iterator moveEnd = d->end(); - while (moveBegin != moveEnd) { - if (QTypeInfo::isComplex) - static_cast(abegin)->~T(); - new (abegin++) T(*moveBegin++); - } - if (abegin < d->end()) { - // destroy rest of instances - destruct(abegin, d->end()); - } - } else { - destruct(abegin, aend); - // QTBUG-53605: static_cast masks clang errors of the form - // error: destination for this 'memmove' call is a pointer to class containing a dynamic class - // FIXME maybe use std::is_polymorphic (as soon as allowed) to avoid the memmove - memmove(static_cast(abegin), static_cast(aend), - (d->size - itemsToErase - itemsUntouched) * sizeof(T)); - } - d->size -= int(itemsToErase); - } - return d->begin() + itemsUntouched; + return d.begin() + i; } template -bool QVector::operator==(const QVector &v) const +inline QVector &QVector::fill(parameter_type t, int newSize) { - if (d == v.d) - return true; - if (d->size != v.d->size) - return false; - const T *vb = v.d->begin(); - const T *b = d->begin(); - const T *e = d->end(); - return std::equal(b, e, QT_MAKE_CHECKED_ARRAY_ITERATOR(vb, v.d->size)); -} - -template -QVector &QVector::fill(const T &from, int asize) -{ - const T copy(from); - resize(asize < 0 ? d->size : asize); - if (d->size) { - T *i = d->end(); - T *b = d->begin(); - while (i != b) - *--i = copy; - } - return *this; -} - -template -QVector &QVector::operator+=(const QVector &l) -{ - if (d->size == 0) { - *this = l; + if (newSize == -1) + newSize = size(); + if (d->needsDetach() || newSize > capacity()) { + // must allocate memory + DataPointer detached(Data::allocate(d->detachCapacity(newSize), + d->detachFlags())); + detached->copyAppend(newSize, t); + d.swap(detached); } else { - uint newSize = d->size + l.d->size; - const bool isTooSmall = newSize > d->allocatedCapacity(); - if (!isDetached() || isTooSmall) { - QArrayData::ArrayOptions opt(isTooSmall ? d->flags | QArrayData::GrowsForward : d->flags); - realloc(isTooSmall ? newSize : d->allocatedCapacity(), opt); - } - - if (l.d->size) { - T *w = d->begin() + newSize; - T *i = l.d->end(); - T *b = l.d->begin(); - while (i != b) { - if (QTypeInfo::isComplex) - new (--w) T(*--i); - else - *--w = *--i; - } - d->size = newSize; - } + // we're detached + const T copy(t); + d->assign(d.begin(), d.begin() + qMin(size(), newSize), t); + if (newSize > size()) + d->copyAppend(newSize - size(), copy); } return *this; } @@ -916,54 +685,35 @@ int lastIndexOf(const QVector &vector, const U &u, int from) } template -int QVector::indexOf(const T &t, int from) const +int QVector::indexOf(const T &t, int from) const noexcept { return QtPrivate::indexOf(*this, t, from); } template -int QVector::lastIndexOf(const T &t, int from) const +int QVector::lastIndexOf(const T &t, int from) const noexcept { return QtPrivate::lastIndexOf(*this, t, from); } template -bool QVector::contains(const T &t) const -{ - const T *b = d->begin(); - const T *e = d->end(); - return std::find(b, e, t) != e; -} - -template -int QVector::count(const T &t) const -{ - const T *b = d->begin(); - const T *e = d->end(); - return int(std::count(b, e, t)); -} - -template -Q_OUTOFLINE_TEMPLATE QVector QVector::mid(int pos, int len) const +inline QVector QVector::mid(int pos, int len) const { using namespace QtPrivate; - switch (QContainerImplHelper::mid(d->size, &pos, &len)) { + switch (QContainerImplHelper::mid(d.size, &pos, &len)) { case QContainerImplHelper::Null: case QContainerImplHelper::Empty: - return QVector(); + return QVector(); case QContainerImplHelper::Full: return *this; case QContainerImplHelper::Subset: break; } - QVector midResult; - midResult.realloc(len, QArrayData::DefaultAllocationFlags); - T *srcFrom = d->begin() + pos; - T *srcTo = d->begin() + pos + len; - midResult.copyConstruct(srcFrom, srcTo, midResult.data()); - midResult.d->size = len; - return midResult; + // Allocate memory + DataPointer copied(Data::allocate(len)); + copied->copyAppend(constBegin() + pos, constBegin() + pos + len); + return copied; } Q_DECLARE_SEQUENTIAL_ITERATOR(Vector) diff --git a/src/corelib/tools/tools.pri b/src/corelib/tools/tools.pri index 40c84157cd..a1b243dd5f 100644 --- a/src/corelib/tools/tools.pri +++ b/src/corelib/tools/tools.pri @@ -47,7 +47,6 @@ HEADERS += \ tools/qvector.h \ tools/qversionnumber.h - SOURCES += \ tools/qarraydata.cpp \ tools/qbitarray.cpp \ diff --git a/src/sql/kernel/qsqlindex.cpp b/src/sql/kernel/qsqlindex.cpp index 60b0ef6965..5781f24b5c 100644 --- a/src/sql/kernel/qsqlindex.cpp +++ b/src/sql/kernel/qsqlindex.cpp @@ -44,9 +44,6 @@ QT_BEGIN_NAMESPACE -// ### Qt 6: remove the static assertion, the 'sorts' field was changed from QList to QVector in Qt 5.6 -Q_STATIC_ASSERT((sizeof(QList) == sizeof(QVector))); - /*! \class QSqlIndex \brief The QSqlIndex class provides functions to manipulate and diff --git a/tests/auto/corelib/tools/qvector/tst_qvector.cpp b/tests/auto/corelib/tools/qvector/tst_qvector.cpp index ed952954cd..7a69e844d4 100644 --- a/tests/auto/corelib/tools/qvector/tst_qvector.cpp +++ b/tests/auto/corelib/tools/qvector/tst_qvector.cpp @@ -104,7 +104,7 @@ private: static void check(const State state1, const State state2) { - QCOMPARE(state1, state2); + QCOMPARE(int(state1), int(state2)); } }; @@ -174,7 +174,7 @@ private: { // check if c object has been moved QCOMPARE(c, c->that); - QCOMPARE(c->state, Constructed); + QCOMPARE(int(c->state), int(Constructed)); } }; QAtomicInt Custom::counter = 0; @@ -724,16 +724,12 @@ void tst_QVector::capacity() const myvec.remove(3); myvec.remove(3); myvec.remove(3); - // TODO: is this a safe assumption? presumably it won't release memory until shrink(), but can we asser that is true? - QVERIFY(myvec.capacity() >= 6); myvec.squeeze(); QVERIFY(myvec.capacity() >= 3); myvec.remove(0); myvec.remove(0); myvec.remove(0); - // TODO: as above note - QVERIFY(myvec.capacity() >= 3); myvec.squeeze(); QVERIFY(myvec.capacity() == 0); } diff --git a/tests/auto/other/toolsupport/tst_toolsupport.cpp b/tests/auto/other/toolsupport/tst_toolsupport.cpp index ab7bca8322..2440b4a5e9 100644 --- a/tests/auto/other/toolsupport/tst_toolsupport.cpp +++ b/tests/auto/other/toolsupport/tst_toolsupport.cpp @@ -97,14 +97,14 @@ void tst_toolsupport::offsets_data() { QTestData &data = QTest::newRow("sizeof(QObjectData)") << sizeof(QObjectData); - data << 28 << 48; // vptr + 3 ptr + 2 int + ptr + data << 36 << 64; // vptr + 2 ptr + (2*ptr + int) + 2 int + ptr } #if RUN_MEMBER_OFFSET_TEST { QTestData &data = QTest::newRow("QObjectPrivate::extraData") << pmm_to_offsetof(&QObjectPrivate::extraData); - data << 28 << 48; // sizeof(QObjectData) + data << 36 << 64; // sizeof(QObjectData) } { @@ -126,9 +126,9 @@ void tst_toolsupport::offsets_data() #ifdef Q_PROCESSOR_X86 // x86 32-bit has weird alignment rules. Refer to QtPrivate::AlignOf in // qglobal.h for more details. - data << 152 << 224; + data << 176 << 272; #else - data << 156 << 224; + data << 180 << 272; #endif } #endif