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>
This commit is contained in:
parent
18a2c62c07
commit
f564e905c1
@ -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
|
||||
@ -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<void *>(d.storage());
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QVariant::QVariant(const QString &val) noexcept
|
||||
|
||||
|
@ -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
|
||||
|
@ -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});
|
||||
|
Loading…
Reference in New Issue
Block a user