QList: add uninitialized resizes

Creating a QList of a given size, or resizing it to a given size, will
always value-initialize its elements. This commit adds support for
uninitialized construction and resizes. The intended use case is using a
QList as storage-to-be-overwritten:

  QList<int> list(size, Qt::Uninitialized);

  fillWithData(list.data(), list.size);

How do we define "uninitialized":

1) if T is constructible using Qt::Uninitialized, use that;
2) otherwise, default-construct T.

In detail:

1) covers (Qt-ish) datatypes that have a default constructor that
   initializes them, but also a dedicated constructor that doesn't
   initialize (e.g. QPoint, QQuaternion, ...).
2) covers everything else. Default initialization of scalars and
   trivially constructible datatypes will leave them uninitialized.

A type which isn't trivially constructible will still get its default
constructor called (and possibly actually gets initialized); we can't
really do better than that, as we still have to construct objects and
start their lifetimes.

[ChangeLog][QtCore][QList] Added support for uninitialized construction
and resizing.

Change-Id: I32c285c7dddbf7e01475943f24e14e824bb13090
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Giuseppe D'Angelo 2023-10-31 14:05:29 +01:00
parent 277d77029d
commit 73bf1c1a9b
4 changed files with 115 additions and 0 deletions

View File

@ -7,6 +7,7 @@
#include <QtCore/qarraydata.h>
#include <QtCore/qcontainertools_impl.h>
#include <QtCore/qnamespace.h>
#include <memory>
#include <new>
@ -960,6 +961,24 @@ public:
// b might be updated so use [b, n)
this->copyAppend(b, b + n);
}
void appendUninitialized(qsizetype newSize)
{
Q_ASSERT(this->isMutable());
Q_ASSERT(!this->isShared());
Q_ASSERT(newSize > this->size);
Q_ASSERT(newSize - this->size <= this->freeSpaceAtEnd());
T *const b = this->begin();
do {
auto ptr = b + this->size;
if constexpr (std::is_constructible_v<T, Qt::Initialization>)
new (ptr) T(Qt::Uninitialized);
else
new (ptr) T; // not T() -- default-construct
} while (++this->size != newSize);
}
};
} // namespace QtPrivate

View File

@ -10,6 +10,7 @@
#include <QtCore/qhashfunctions.h>
#include <QtCore/qiterator.h>
#include <QtCore/qcontainertools_impl.h>
#include <QtCore/qnamespace.h>
#include <functional>
#include <limits>
@ -324,6 +325,13 @@ public:
inline explicit QList(const String &str)
{ append(str); }
QList(qsizetype size, Qt::Initialization)
: d(size)
{
if (size)
d->appendUninitialized(size);
}
// compiler-generated special member functions are fine!
void swap(QList &other) noexcept { d.swap(other.d); }
@ -404,6 +412,12 @@ public:
if (size > this->size())
d->copyAppend(size - this->size(), c);
}
void resizeForOverwrite(qsizetype size)
{
resize_internal(size);
if (size > this->size())
d->appendUninitialized(size);
}
inline qsizetype capacity() const { return qsizetype(d->constAllocatedCapacity()); }
void reserve(qsizetype size);

View File

@ -247,6 +247,31 @@
\sa resize()
*/
/*! \fn template <typename T> QList<T>::QList(qsizetype size, Qt::Initialization)
\since 6.8
Constructs a list with an initial size of \a size elements.
QList will make an attempt at \b{not initializing} the elements.
//! [qlist-uninitialized-strategy]
Specifically:
\list
\li if \c{T} has a constructor that accepts \c{Qt::Uninitialized},
that constructor will be used to initialize the elements;
\li otherwise, each element is default constructed. For
trivially constructible types (such as \c{int}, \c{float}, etc.)
this is equivalent to not initializing them.
\endlist
//! [qlist-uninitialized-strategy]
\sa resizeForOverwrite()
*/
/*! \fn template <typename T> QList<T>::QList(qsizetype size, parameter_type value)
Constructs a list with an initial size of \a size elements.
@ -444,6 +469,17 @@
\sa size()
*/
/*! \fn template <typename T> void QList<T>::resizeForOverwrite(qsizetype size)
\since 6.8
Sets the size of the list to \a size. If \a size is less than the
current size, elements are removed from the end. If \a size is
greater than the current size, elements are added to the end; QList
will make an attempt at \b{not initializing} these new elements.
\include qlist.qdoc qlist-uninitialized-strategy
*/
/*! \fn template <typename T> qsizetype QList<T>::capacity() const
Returns the maximum number of items that can be stored in the

View File

@ -322,6 +322,7 @@ private slots:
void resizeToZero() const;
void resizeToTheSameSize_data();
void resizeToTheSameSize() const;
void resizeForOverwrite() const;
void iterators() const;
void constIterators() const;
void reverseIterators() const;
@ -2531,6 +2532,51 @@ void tst_QList::resizeToTheSameSize() const
QCOMPARE(y.size(), x.size());
}
void tst_QList::resizeForOverwrite() const
{
constexpr int BUILD_COUNT = 42;
{
// Smoke test
QList<int> l(BUILD_COUNT, Qt::Uninitialized);
l.resizeForOverwrite(l.size() + BUILD_COUNT);
}
{
const int beforeCounter = Movable::counter.loadRelaxed();
QList<Movable> l(BUILD_COUNT, Qt::Uninitialized);
const int after1Counter = Movable::counter.loadRelaxed();
QCOMPARE(after1Counter, beforeCounter + BUILD_COUNT);
l.resizeForOverwrite(l.size() + BUILD_COUNT);
const int after2Counter = Movable::counter.loadRelaxed();
QCOMPARE(after2Counter, after1Counter + BUILD_COUNT);
}
struct QtInitializationSupport {
bool wasInitialized;
QtInitializationSupport() : wasInitialized(true) {}
explicit QtInitializationSupport(Qt::Initialization) : wasInitialized(false) {}
};
{
QList<QtInitializationSupport> l(BUILD_COUNT);
for (const auto &elem : l)
QVERIFY(elem.wasInitialized);
l.resize(l.size() + BUILD_COUNT);
for (const auto &elem : l)
QVERIFY(elem.wasInitialized);
}
{
QList<QtInitializationSupport> l(BUILD_COUNT, Qt::Uninitialized);
for (const auto &elem : l)
QVERIFY(!elem.wasInitialized);
l.resizeForOverwrite(l.size() + BUILD_COUNT);
for (const auto &elem : l)
QVERIFY(!elem.wasInitialized);
}
}
void tst_QList::iterators() const
{
QList<int> v;