diff --git a/src/corelib/tools/qarraydataops.h b/src/corelib/tools/qarraydataops.h index 957fa9e218f..1fd7e99c42e 100644 --- a/src/corelib/tools/qarraydataops.h +++ b/src/corelib/tools/qarraydataops.h @@ -772,10 +772,31 @@ public: Q_ASSERT(where >= this->begin() && where <= this->end()); Q_ASSERT(this->freeSpaceAtEnd() >= 1); - createInPlace(this->end(), std::forward(args)...); - ++this->size; + if (where == this->end()) { + createInPlace(this->end(), std::forward(args)...); + ++this->size; + } else { + T tmp(std::forward(args)...); - std::rotate(where, this->end() - 1, this->end()); + T *const end = this->end(); + T *readIter = end - 1; + T *writeIter = end; + + // Create new element at the end + new (writeIter) T(std::move(*readIter)); + ++this->size; + + // Move assign over existing elements + while (readIter != where) { + --readIter; + --writeIter; + *writeIter = std::move(*readIter); + } + + // Assign new element + --writeIter; + *writeIter = std::move(tmp); + } } template @@ -785,11 +806,35 @@ public: Q_ASSERT(where >= this->begin() && where <= this->end()); Q_ASSERT(this->freeSpaceAtBegin() >= 1); - createInPlace(this->begin() - 1, std::forward(args)...); - --this->ptr; - ++this->size; + if (where == this->begin()) { + createInPlace(this->begin() - 1, std::forward(args)...); + --this->ptr; + ++this->size; + } else { + T tmp(std::forward(args)...); - std::rotate(this->begin(), this->begin() + 1, where); + T *const begin = this->begin(); + T *readIter = begin; + T *writeIter = begin - 1; + + // Create new element at the beginning + new (writeIter) T(std::move(*readIter)); + --this->ptr; + ++this->size; + + ++readIter; + ++writeIter; + + // Move assign over existing elements + while (readIter != where) { + *writeIter = std::move(*readIter); + ++readIter; + ++writeIter; + } + + // Assign new element + *writeIter = std::move(tmp); + } } void erase(T *b, T *e) @@ -998,6 +1043,54 @@ public: // use moving insert using QGenericArrayOps::insert; + template + void emplace(iterator where, Args &&... args) + { + emplace(GrowsForwardTag {}, where, std::forward(args)...); + } + + template + void emplace(GrowsForwardTag, iterator where, Args &&... args) + { + Q_ASSERT(!this->isShared()); + Q_ASSERT(where >= this->begin() && where <= this->end()); + Q_ASSERT(this->freeSpaceAtEnd() >= 1); + + if (where == this->end()) { + this->createInPlace(where, std::forward(args)...); + } else { + T tmp(std::forward(args)...); + typedef typename QArrayExceptionSafetyPrimitives::Displacer ReversibleDisplace; + ReversibleDisplace displace(where, this->end(), 1); + this->createInPlace(where, std::move(tmp)); + displace.commit(); + } + ++this->size; + } + + template + void emplace(GrowsBackwardsTag, iterator where, Args &&... args) + { + Q_ASSERT(!this->isShared()); + Q_ASSERT(where >= this->begin() && where <= this->end()); + Q_ASSERT(this->freeSpaceAtBegin() >= 1); + + if (where == this->begin()) { + this->createInPlace(where - 1, std::forward(args)...); + } else { + T tmp(std::forward(args)...); + typedef typename QArrayExceptionSafetyPrimitives::Displacer ReversibleDisplace; + ReversibleDisplace displace(this->begin(), where, -1); + this->createInPlace(where - 1, std::move(tmp)); + displace.commit(); + } + --this->ptr; + ++this->size; + } + + // use moving emplace + using QGenericArrayOps::emplace; + void erase(T *b, T *e) { erase(GrowsForwardTag{}, b, e); } diff --git a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp index f676307730a..bb76caed3e8 100644 --- a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp +++ b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp @@ -82,6 +82,8 @@ private slots: void freeSpace(); void dataPointerAllocate_data(); void dataPointerAllocate(); + void selfEmplaceBackwards(); + void selfEmplaceForward(); #ifndef QT_NO_EXCEPTIONS void exceptionSafetyPrimitives_constructor(); void exceptionSafetyPrimitives_destructor(); @@ -2128,6 +2130,157 @@ void tst_QArrayData::dataPointerAllocate() } } +struct MyQStringWrapper : public QString +{ + bool movedTo = false; + bool movedFrom = false; + MyQStringWrapper() = default; + MyQStringWrapper(QChar c) : QString(c) { } + MyQStringWrapper(MyQStringWrapper &&other) : QString(std::move(static_cast(other))) + { + movedTo = true; + movedFrom = other.movedFrom; + other.movedFrom = true; + } + MyQStringWrapper &operator=(MyQStringWrapper &&other) + { + QString::operator=(std::move(static_cast(other))); + movedTo = true; + movedFrom = other.movedFrom; + other.movedFrom = true; + return *this; + } + MyQStringWrapper(const MyQStringWrapper &) = default; + MyQStringWrapper &operator=(const MyQStringWrapper &) = default; + ~MyQStringWrapper() = default; +}; + +struct MyMovableQString : public MyQStringWrapper +{ + MyMovableQString() = default; + MyMovableQString(QChar c) : MyQStringWrapper(c) { } + +private: + friend bool operator==(const MyMovableQString &a, QChar c) + { + return static_cast(a) == QString(c); + } + + friend bool operator==(const MyMovableQString &a, const MyMovableQString &b) + { + return static_cast(a) == static_cast(b); + } +}; + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(MyMovableQString, Q_RELOCATABLE_TYPE); +QT_END_NAMESPACE +static_assert(QTypeInfo::isComplex); +static_assert(QTypeInfo::isRelocatable); + +struct MyComplexQString : public MyQStringWrapper +{ + MyComplexQString() = default; + MyComplexQString(QChar c) : MyQStringWrapper(c) { } + +private: + friend bool operator==(const MyComplexQString &a, QChar c) + { + return static_cast(a) == QString(c); + } + + friend bool operator==(const MyComplexQString &a, const MyComplexQString &b) + { + return static_cast(a) == static_cast(b); + } +}; +static_assert(QTypeInfo::isComplex); +static_assert(!QTypeInfo::isRelocatable); + +void tst_QArrayData::selfEmplaceBackwards() +{ + const auto createDataPointer = [](qsizetype capacity, int spaceAtEnd, auto dummy) { + using Type = std::decay_t; + Q_UNUSED(dummy); + auto [header, ptr] = QTypedArrayData::allocate(capacity, QArrayData::Grow); + // do custom adjustments to make sure there's free space at end + ptr += header->alloc - spaceAtEnd; + return QArrayDataPointer(header, ptr); + }; + + const auto testSelfEmplace = [&](auto dummy, int spaceAtEnd, auto initValues) { + auto adp = createDataPointer(100, spaceAtEnd, dummy); + for (auto v : initValues) { + adp->emplaceBack(v); + } + QVERIFY(!adp.freeSpaceAtEnd()); + QVERIFY(adp.freeSpaceAtBegin()); + + adp->emplace(adp.end(), adp.data()[0]); + for (qsizetype i = 0; i < adp.size - 1; ++i) { + QCOMPARE(adp.data()[i], initValues[i]); + } + QCOMPARE(adp.data()[adp.size - 1], initValues[0]); + + adp->emplace(adp.end(), std::move(adp.data()[0])); + for (qsizetype i = 1; i < adp.size - 2; ++i) { + QCOMPARE(adp.data()[i], initValues[i]); + } + QCOMPARE(adp.data()[adp.size - 2], initValues[0]); + QCOMPARE(adp.data()[0].movedFrom, true); + QCOMPARE(adp.data()[adp.size - 1], initValues[0]); + QCOMPARE(adp.data()[adp.size - 1].movedTo, true); + }; + + QList movableObjs { u'a', u'b', u'c', u'd' }; + RUN_TEST_FUNC(testSelfEmplace, MyMovableQString(), 4, movableObjs); + QList complexObjs { u'a', u'b', u'c', u'd' }; + RUN_TEST_FUNC(testSelfEmplace, MyComplexQString(), 4, complexObjs); +} + +void tst_QArrayData::selfEmplaceForward() +{ + const auto createDataPointer = [](qsizetype capacity, int spaceAtBegin, auto dummy) { + using Type = std::decay_t; + Q_UNUSED(dummy); + auto [header, ptr] = QTypedArrayData::allocate(capacity, QArrayData::Grow); + // do custom adjustments to make sure there's free space at end + ptr += spaceAtBegin; + return QArrayDataPointer(header, ptr); + }; + + const auto testSelfEmplace = [&](auto dummy, int spaceAtBegin, auto initValues) { + auto adp = createDataPointer(100, spaceAtBegin, dummy); + auto reversedInitValues = initValues; + std::reverse(reversedInitValues.begin(), reversedInitValues.end()); + for (auto v : reversedInitValues) { + adp->emplaceFront(v); + } + QVERIFY(!adp.freeSpaceAtBegin()); + QVERIFY(adp.freeSpaceAtEnd()); + + adp->emplace(adp.begin(), adp.data()[adp.size - 1]); + for (qsizetype i = 1; i < adp.size; ++i) { + QCOMPARE(adp.data()[i], initValues[i - 1]); + } + QCOMPARE(adp.data()[0], initValues[spaceAtBegin - 1]); + + adp->emplace(adp.begin(), std::move(adp.data()[adp.size - 1])); + for (qsizetype i = 2; i < adp.size; ++i) { + QCOMPARE(adp.data()[i], initValues[i - 2]); + } + QCOMPARE(adp.data()[1], initValues[spaceAtBegin - 1]); + QCOMPARE(adp.data()[adp.size - 1].movedFrom, true); + QCOMPARE(adp.data()[0], initValues[spaceAtBegin - 1]); + QCOMPARE(adp.data()[0].movedTo, true); + }; + + QList movableObjs { u'a', u'b', u'c', u'd' }; + RUN_TEST_FUNC(testSelfEmplace, MyMovableQString(), 4, movableObjs); + QList complexObjs { u'a', u'b', u'c', u'd' }; + RUN_TEST_FUNC(testSelfEmplace, MyComplexQString(), 4, complexObjs); +} + #ifndef QT_NO_EXCEPTIONS struct ThrowingTypeWatcher { diff --git a/tests/benchmarks/corelib/tools/qlist/main.cpp b/tests/benchmarks/corelib/tools/qlist/main.cpp index 716a3061ae2..223e1603e16 100644 --- a/tests/benchmarks/corelib/tools/qlist/main.cpp +++ b/tests/benchmarks/corelib/tools/qlist/main.cpp @@ -184,11 +184,11 @@ private Q_SLOTS: void appendPrependOne_complex_data() const { commonBenchmark_data(); } void appendPrependOne_QString_data() const { commonBenchmark_data(); } - void appendPrependOne_int() const { midInsertOne_impl(); } - void appendPrependOne_primitive() const { midInsertOne_impl(); } - void appendPrependOne_movable() const { midInsertOne_impl(); } - void appendPrependOne_complex() const { midInsertOne_impl(); } - void appendPrependOne_QString() const { midInsertOne_impl(); } + void appendPrependOne_int() const { appendPrependOne_impl(); } + void appendPrependOne_primitive() const { appendPrependOne_impl(); } + void appendPrependOne_movable() const { appendPrependOne_impl(); } + void appendPrependOne_complex() const { appendPrependOne_impl(); } + void appendPrependOne_QString() const { appendPrependOne_impl(); } // prepend half elements, then appen another half: void prependAppendHalvesOne_int_data() const { commonBenchmark_data(); } @@ -197,11 +197,27 @@ private Q_SLOTS: void prependAppendHalvesOne_complex_data() const { commonBenchmark_data(); } void prependAppendHalvesOne_QString_data() const { commonBenchmark_data(); } - void prependAppendHalvesOne_int() const { midInsertOne_impl(); } - void prependAppendHalvesOne_primitive() const { midInsertOne_impl(); } - void prependAppendHalvesOne_movable() const { midInsertOne_impl(); } - void prependAppendHalvesOne_complex() const { midInsertOne_impl(); } - void prependAppendHalvesOne_QString() const { midInsertOne_impl(); } + void prependAppendHalvesOne_int() const { prependAppendHalvesOne_impl(); } + void prependAppendHalvesOne_primitive() const + { + prependAppendHalvesOne_impl(); + } + void prependAppendHalvesOne_movable() const { prependAppendHalvesOne_impl(); } + void prependAppendHalvesOne_complex() const { prependAppendHalvesOne_impl(); } + void prependAppendHalvesOne_QString() const { prependAppendHalvesOne_impl(); } + + // emplace in middle 1 element: + void midEmplaceOne_int_data() const { commonBenchmark_data(); } + void midEmplaceOne_primitive_data() const { commonBenchmark_data(); } + void midEmplaceOne_movable_data() const { commonBenchmark_data(); } + void midEmplaceOne_complex_data() const { commonBenchmark_data(); } + void midEmplaceOne_QString_data() const { commonBenchmark_data(); } + + void midEmplaceOne_int() const { midEmplaceOne_impl(); } + void midEmplaceOne_primitive() const { midEmplaceOne_impl(); } + void midEmplaceOne_movable() const { midEmplaceOne_impl(); } + void midEmplaceOne_complex() const { midEmplaceOne_impl(); } + void midEmplaceOne_QString() const { midEmplaceOne_impl(); } // For 5.15 we also want to compare against QVector #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) @@ -251,11 +267,14 @@ private Q_SLOTS: void qvector_appendPrependOne_complex_data() const { commonBenchmark_data(); } void qvector_appendPrependOne_QString_data() const { commonBenchmark_data(); } - void qvector_appendPrependOne_int() const { midInsertOne_impl(); } - void qvector_appendPrependOne_primitive() const { midInsertOne_impl(); } - void qvector_appendPrependOne_movable() const { midInsertOne_impl(); } - void qvector_appendPrependOne_complex() const { midInsertOne_impl(); } - void qvector_appendPrependOne_QString() const { midInsertOne_impl(); } + void qvector_appendPrependOne_int() const { appendPrependOne_impl(); } + void qvector_appendPrependOne_primitive() const + { + appendPrependOne_impl(); + } + void qvector_appendPrependOne_movable() const { appendPrependOne_impl(); } + void qvector_appendPrependOne_complex() const { appendPrependOne_impl(); } + void qvector_appendPrependOne_QString() const { appendPrependOne_impl(); } // prepend half elements, then appen another half: void qvector_prependAppendHalvesOne_int_data() const { commonBenchmark_data(); } @@ -267,14 +286,36 @@ private Q_SLOTS: void qvector_prependAppendHalvesOne_complex_data() const { commonBenchmark_data(); } void qvector_prependAppendHalvesOne_QString_data() const { commonBenchmark_data(); } - void qvector_prependAppendHalvesOne_int() const { midInsertOne_impl(); } + void qvector_prependAppendHalvesOne_int() const { prependAppendHalvesOne_impl(); } void qvector_prependAppendHalvesOne_primitive() const { - midInsertOne_impl(); + prependAppendHalvesOne_impl(); } - void qvector_prependAppendHalvesOne_movable() const { midInsertOne_impl(); } - void qvector_prependAppendHalvesOne_complex() const { midInsertOne_impl(); } - void qvector_prependAppendHalvesOne_QString() const { midInsertOne_impl(); } + void qvector_prependAppendHalvesOne_movable() const + { + prependAppendHalvesOne_impl(); + } + void qvector_prependAppendHalvesOne_complex() const + { + prependAppendHalvesOne_impl(); + } + void qvector_prependAppendHalvesOne_QString() const + { + prependAppendHalvesOne_impl(); + } + + // emplace in middle 1 element: + void qvector_midEmplaceOne_int_data() const { commonBenchmark_data(); } + void qvector_midEmplaceOne_primitive_data() const { commonBenchmark_data(); } + void qvector_midEmplaceOne_movable_data() const { commonBenchmark_data(); } + void qvector_midEmplaceOne_complex_data() const { commonBenchmark_data(); } + void qvector_midEmplaceOne_QString_data() const { commonBenchmark_data(); } + + void qvector_midEmplaceOne_int() const { midEmplaceOne_impl(); } + void qvector_midEmplaceOne_primitive() const { midEmplaceOne_impl(); } + void qvector_midEmplaceOne_movable() const { midEmplaceOne_impl(); } + void qvector_midEmplaceOne_complex() const { midEmplaceOne_impl(); } + void qvector_midEmplaceOne_QString() const { midEmplaceOne_impl(); } #endif private: @@ -295,6 +336,9 @@ private: template typename, typename> void prependAppendHalvesOne_impl() const; + + template typename, typename> + void midEmplaceOne_impl() const; }; template @@ -495,6 +539,22 @@ void tst_QList::prependAppendHalvesOne_impl() const } } +template typename Container, typename T> +void tst_QList::midEmplaceOne_impl() const +{ + QFETCH(int, elemCount); + constexpr auto getValue = []() { return T {}; }; + + QBENCHMARK { + Container container; + auto lvalue = getValue(); + + for (int i = 0; i < elemCount; ++i) { + container.emplace(container.size() / 2, lvalue); + } + } +} + QTEST_APPLESS_MAIN(tst_QList) #include "main.moc"