Skip to content

Commit

Permalink
QList: add uninitialized resizes
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
dangelog committed Feb 16, 2024
1 parent 277d770 commit 73bf1c1
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/corelib/tools/qarraydataops.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <QtCore/qarraydata.h>
#include <QtCore/qcontainertools_impl.h>
#include <QtCore/qnamespace.h>

#include <memory>
#include <new>
Expand Down Expand Up @@ -960,6 +961,24 @@ struct QCommonArrayOps : QArrayOpsSelector<T>::Type
// 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
Expand Down
14 changes: 14 additions & 0 deletions src/corelib/tools/qlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -324,6 +325,13 @@ class QList
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); }
Expand Down Expand Up @@ -404,6 +412,12 @@ class QList
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);
Expand Down
36 changes: 36 additions & 0 deletions src/corelib/tools/qlist.qdoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions tests/auto/corelib/tools/qlist/tst_qlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 73bf1c1

Please sign in to comment.