From f564e905c1c258691603694aaa70303b029e4598 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Thu, 23 Mar 2023 10:12:10 +0100 Subject: QVariant: Support emplace [ChangeLog][QtCore][QVariant] Implemented in-place construction for QVariant. The constructor taking std::in_place_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 Reviewed-by: Ivan Solovev --- src/corelib/kernel/qvariant.cpp | 63 +++++++++++++++++++++++++++++++++++++++++ src/corelib/kernel/qvariant.h | 39 +++++++++++++++++++++++++ 2 files changed, 102 insertions(+) (limited to 'src') 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 = 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 &, Args...> = true> QVariant::emplace(std::initializer_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 @@ -555,6 +577,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(d.storage()); +} + /*! \fn QVariant::QVariant(const QString &val) noexcept 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 + void verifySuitableForEmplace() + { + static_assert(!std::is_reference_v, + "QVariant does not support reference types"); + static_assert(!std::is_const_v, + "QVariant does not support const types"); + static_assert(std::is_copy_constructible_v, + "QVariant requires that the type is copyable"); + static_assert(std::is_destructible_v, + "QVariant requires that the type is destructible"); + } + + template + Type &emplaceImpl(Args&&... args) + { + verifySuitableForEmplace(); + auto data = static_cast(prepareForEmplace(QMetaType::fromType())); + return *q20::construct_at(data, std::forward(args)...); + } + +public: + template = true> + Type &emplace(Args&&... args) + { + return emplaceImpl(std::forward(args)...); + } + + template &, Args...> = true> + Type &emplace(std::initializer_list list, Args&&... args) + { + return emplaceImpl(list, std::forward(args)...); + } + template, 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 -- cgit v1.2.3