diff options
author | Fabian Kosmale <fabian.kosmale@qt.io> | 2023-03-23 10:12:10 +0100 |
---|---|---|
committer | Fabian Kosmale <fabian.kosmale@qt.io> | 2023-06-02 09:13:08 +0200 |
commit | f564e905c1c258691603694aaa70303b029e4598 (patch) | |
tree | ed4e6da24fa75c6df7bf400e01535cdde87b506d | |
parent | 18a2c62c073371ab24685d0f98b4b0cc04d11a7e (diff) |
QVariant: Support emplace
[ChangeLog][QtCore][QVariant] Implemented in-place construction for
QVariant. The constructor taking std::in_place_type<Type> constructs
an object of type Type directly inside QVariant's storage, without any
further copy or move operations. QVariant::emplace() does the same
when replacing the content of an existing QVariant and tries to reuse
previously-allocated memory.
Fixes: QTBUG-112187
Change-Id: I16614ad701fa3bb583976ed2001bb312f119a51f
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
-rw-r--r-- | src/corelib/kernel/qvariant.cpp | 63 | ||||
-rw-r--r-- | src/corelib/kernel/qvariant.h | 39 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp | 98 |
3 files changed, 200 insertions, 0 deletions
diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index 882abdf5b4..b5783be83f 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -545,6 +545,28 @@ QVariant::QVariant(const QVariant &p) non-initializer list \c{in_place_type_t} overload. */ + +/*! + \fn template <typename Type, typename... Args, if_constructible<Type, Args...> = true> QVariant::emplace(Args&&... args) + + \since 6.6 + Replaces the object currently held in \c{*this} with an object of + type \c{Type}, constructed from \a{args}\c{...}. If \c{*this} was non-null, + the previously held object is destroyed first. + If possible, this method will reuse memory allocated by the QVariant. + Returns a reference to the newly-created object. + */ + +/*! + \fn template <typename Type, typename List, typename... Args, if_constructible<Type, std::initializer_list<List> &, Args...> = true> QVariant::emplace(std::initializer_list<List> list, Args&&... args) + + \since 6.6 + \overload + This overload exists to support types with constructors taking an + \c initializer_list. It behaves otherwise equivalent to the + non-initializer list overload. +*/ + QVariant::QVariant(std::in_place_t, QMetaType type) : d(type.iface()) { // we query the metatype instead of detecting it at compile time @@ -556,6 +578,47 @@ QVariant::QVariant(std::in_place_t, QMetaType type) : d(type.iface()) } /*! + \internal + Returns a pointer to data suitable for placement new + of an object of type \a type + Changes the variant's metatype to \a type + */ +void *QVariant::prepareForEmplace(QMetaType type) +{ + /* There are two cases where we can reuse the existing storage + (1) The new type fits in QVariant's SBO storage + (2) We are using the externally allocated storage, the variant is + detached, and the new type fits into the existing storage. + In all other cases (3), we cannot reuse the storage. + */ + auto typeFits = [&] { + auto newIface = type.iface(); + auto oldIface = d.typeInterface(); + auto newSize = PrivateShared::computeAllocationSize(newIface->size, newIface->alignment); + auto oldSize = PrivateShared::computeAllocationSize(oldIface->size, oldIface->alignment); + return newSize <= oldSize; + }; + if (Private::canUseInternalSpace(type.iface())) { // (1) + clear(); + d.packedType = quintptr(type.iface()) >> 2; + return d.data.data; + } else if (d.is_shared && isDetached() && typeFits()) { // (2) + QtMetaTypePrivate::destruct(d.typeInterface(), d.data.shared->data()); + // compare QVariant::PrivateShared::create + const auto ps = d.data.shared; + const auto align = type.alignOf(); + ps->offset = PrivateShared::computeOffset(ps, align); + d.packedType = quintptr(type.iface()) >> 2; + return ps->data(); + } + // (3) + QVariant newVariant(std::in_place, type); + swap(newVariant); + // const cast is safe, we're in a non-const method + return const_cast<void *>(d.storage()); +} + +/*! \fn QVariant::QVariant(const QString &val) noexcept Constructs a new variant with a string value, \a val. diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index 9ed6fedd3c..00e2bb0072 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -444,6 +444,43 @@ public: { return d.storage(); } inline const void *data() const { return constData(); } +private: + template <typename Type> + void verifySuitableForEmplace() + { + static_assert(!std::is_reference_v<Type>, + "QVariant does not support reference types"); + static_assert(!std::is_const_v<Type>, + "QVariant does not support const types"); + static_assert(std::is_copy_constructible_v<Type>, + "QVariant requires that the type is copyable"); + static_assert(std::is_destructible_v<Type>, + "QVariant requires that the type is destructible"); + } + + template <typename Type, typename... Args> + Type &emplaceImpl(Args&&... args) + { + verifySuitableForEmplace<Type>(); + auto data = static_cast<Type *>(prepareForEmplace(QMetaType::fromType<Type>())); + return *q20::construct_at(data, std::forward<Args>(args)...); + } + +public: + template <typename Type, typename... Args, + if_constructible<Type, Args...> = true> + Type &emplace(Args&&... args) + { + return emplaceImpl<Type>(std::forward<Args>(args)...); + } + + template <typename Type, typename List, typename... Args, + if_constructible<Type, std::initializer_list<List> &, Args...> = true> + Type &emplace(std::initializer_list<List> list, Args&&... args) + { + return emplaceImpl<Type>(list, std::forward<Args>(args)...); + } + template<typename T, typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, QVariant>>> void setValue(T &&avalue) { @@ -580,6 +617,8 @@ private: // used to setup the QVariant internals for the "real" inplace ctor QVariant(std::in_place_t, QMetaType type); + // helper for emplace + void *prepareForEmplace(QMetaType type); // These constructors don't create QVariants of the type associated // with the enum, as expected, but they would create a QVariant of diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index 4fa42a1d52..feb03a029f 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -380,6 +380,7 @@ private slots: void copyNonDefaultConstructible(); void inplaceConstruct(); + void emplace(); void getIf_int() { getIf_impl(42); } void getIf_QString() { getIf_impl(u"string"_s); }; @@ -5767,6 +5768,103 @@ void tst_QVariant::inplaceConstruct() } } +struct LargerThanInternalQVariantStorage { + char data[6 * sizeof(void *)]; +}; + +struct alignas(256) LargerThanInternalQVariantStorageOveraligned { + char data[6 * sizeof(void *)]; +}; + +struct alignas(128) SmallerAlignmentEvenLargerSize { + char data[17 * sizeof(void *)]; +}; + +void tst_QVariant::emplace() +{ + { + // can emplace non default constructible + can emplace on null variant + NonDefaultConstructible ndc(42); + QVariant var; + var.emplace<NonDefaultConstructible>(42); + QVERIFY(get_if<NonDefaultConstructible>(&var)); + QCOMPARE(get<NonDefaultConstructible>(var), ndc); + } + { + // can emplace using ctor taking initializer_list + QVariant var; + var.emplace<std::vector<int>>({0, 1, 2, 3, 4}); + auto vecPtr = get_if<std::vector<int>>(&var); + QVERIFY(vecPtr); + QCOMPARE(vecPtr->size(), 5U); + for (int i = 0; i < 5; ++i) + QCOMPARE(vecPtr->at(size_t(i)), i); + } + // prequisites for the test + QCOMPARE_LE(sizeof(std::vector<int>), sizeof(std::string)); + QCOMPARE(alignof(std::vector<int>), alignof(std::string)); + { + // emplace can reuse storage + auto var = QVariant::fromValue(std::string{}); + QVERIFY(var.data_ptr().is_shared); + auto data = var.constData(); + std::vector<int> &vec = var.emplace<std::vector<int>>(3, 42); + /* alignment is the same, so the pointer is exactly the same; + no offset change */ + auto expected = std::vector<int>{42, 42, 42}; + QCOMPARE(get_if<std::vector<int>>(&var), &vec); + QCOMPARE(get<std::vector<int>>(var), expected); + QCOMPARE(var.constData(), data); + } + { + // emplace can't reuse storage if the variant is shared + auto var = QVariant::fromValue(std::string{}); + [[maybe_unused]] QVariant causesSharing = var; + QVERIFY(var.data_ptr().is_shared); + auto data = var.constData(); + var.emplace<std::vector<int>>(3, 42); + auto expected = std::vector<int>{42, 42, 42}; + QVERIFY(get_if<std::vector<int>>(&var)); + QCOMPARE(get<std::vector<int>>(var), expected); + QCOMPARE_NE(var.constData(), data); + } + { + // emplace puts element into the correct place - non-shared + QVERIFY(QVariant::Private::canUseInternalSpace(QMetaType::fromType<QString>().iface())); + QVariant var; + var.emplace<QString>(QChar('x')); + QVERIFY(!var.data_ptr().is_shared); + } + { + // emplace puts element into the correct place - shared + QVERIFY(!QVariant::Private::canUseInternalSpace(QMetaType::fromType<std::string>().iface())); + QVariant var; + var.emplace<std::string>(42, 'x'); + QVERIFY(var.data_ptr().is_shared); + } + { + // emplace does not reuse the storage if alignment is too large + auto iface = QMetaType::fromType<LargerThanInternalQVariantStorage>().iface(); + QVERIFY(!QVariant::Private::canUseInternalSpace(iface)); + auto var = QVariant::fromValue(LargerThanInternalQVariantStorage{}); + auto data = var.constData(); + var.emplace<LargerThanInternalQVariantStorageOveraligned>(); + QCOMPARE_NE(var.constData(), data); + } + { + // emplace does reuse the storage if new alignment and size are together small enough + auto iface = QMetaType::fromType<LargerThanInternalQVariantStorageOveraligned>().iface(); + QVERIFY(!QVariant::Private::canUseInternalSpace(iface)); + auto var = QVariant::fromValue(LargerThanInternalQVariantStorageOveraligned{}); + auto data = var.constData(); + var.emplace<SmallerAlignmentEvenLargerSize>(); + // no exact match below - the alignment is after all different + QCOMPARE_LE(quintptr(var.constData()), quintptr(data)); + QCOMPARE_LE(quintptr(var.constData()), + quintptr(data) + sizeof(LargerThanInternalQVariantStorageOveraligned)); + } +} + void tst_QVariant::getIf_NonDefaultConstructible() { getIf_impl(NonDefaultConstructible{42}); |